diff --git a/tests/token_test.module b/tests/token_test.module
index 3268b9d..4b37e74 100644
--- a/tests/token_test.module
+++ b/tests/token_test.module
@@ -26,6 +26,9 @@ function token_test_token_list($type = 'all') {
     $tokens['token_test']['gamma_?']       = t("Token test Gamme with wildcard.");
     $tokens['token_test']['delta-?']         = t("Token test Delta with wildcard.");
     $tokens['token_test']['epsilon-zeta-????']  = t("Token test Epsilon-Zeta with wildcard.");
+
+    // Provide a token that returns a different value to test $options caching.
+    $tokens['token_test']['option-foo'] = t('A token with various values.');
   }
 
   return $tokens;
@@ -45,6 +48,8 @@ function token_test_token_values($type, $object = NULL, $options = array()) {
     $tokens['gamma_A']         = 'Gamma plus A';
     $tokens['delta-extra']     = 'Delta plus extra';
     $tokens['epsilon-zeta-A']  = 'Epsilon-Zeta plus A';
+
+    $tokens['option-foo'] = !empty($options['foo']) ? $options['foo'] : '';
   }
 
   return $tokens;
diff --git a/token.module b/token.module
index 8cf127a..ef8e21e 100644
--- a/token.module
+++ b/token.module
@@ -309,21 +309,8 @@ function token_replace_multiple($text, $types = array('global' => NULL), $leadin
  * should be run through token_prepare_tokens().
  */
 function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $options = array()) {
-  static $tokens;
-  static $running;
-
-  // Flush the static token cache. Useful for processes that need to slog through
-  // huge numbers of tokens in a single execution cycle. Flushing it will keep
-  // them from burning through memory.
-  if ($flush || !isset($tokens)) {
-    $tokens = array();
-  }
-
-  // Since objects in PHP5 are always passed by reference, we ensure we're
-  // working on a copy of the object.
-  if (is_object($object)) {
-    $object = drupal_clone($object);
-  }
+  static $tokens = array();
+  static $running = FALSE;
 
   // Simple recursion check. This is to avoid content_view()'s potential
   // for endless looping when a filter uses tokens, which load the content
@@ -336,27 +323,46 @@ function token_get_values($type = 'global', $object = NULL, $flush = FALSE, $opt
     $result->values = array();
     return $result;
   }
+  else {
+    $running = TRUE;
+  }
 
-  $running = TRUE;
-
-  token_include();
+  // Flush the static token cache. Useful for processes that need to slog
+  // through huge numbers of tokens in a single execution cycle. Flushing it
+  // will keep them from burning through memory.
+  if ($flush) {
+    $tokens = array();
+  }
 
-  $id = _token_get_id($type, $object);
-  if ($id && isset($tokens[$type][$id])) {
-    $tmp_tokens = $tokens[$type][$id];
+  // Since objects in PHP5 are always passed by reference, we ensure we're
+  // working on a copy of the object.
+  if (is_object($object)) {
+    $object = drupal_clone($object);
   }
-  else {
-    $tmp_tokens = module_invoke_all('token_values', $type, $object, $options);
-    $tokens[$type][$id] = $tmp_tokens;
+
+  // Neutralize options that do not affect token replacement.
+  $serialized_options = $options;
+  unset($serialized_options['clear']);
+
+  // Store the token cache by object ID and serialized options.
+  $cid = _token_get_id($type, $object) . ':' . md5(serialize($serialized_options));
+  if ($type != 'global' && !isset($tokens[$type][$cid])) {
+    token_include();
+    $tokens[$type][$cid] = module_invoke_all('token_values', $type, $object, $options);
   }
 
   // Special-case global tokens, as we always want to be able to process
   // those substitutions.
-  if (!isset($tokens['global']['default'])) {
-    $tokens['global']['default'] = module_invoke_all('token_values', 'global');
+  if (!isset($tokens['global'][$cid])) {
+    token_include();
+    $tokens['global'][$cid] = module_invoke_all('token_values', 'global', NULL, $options);
   }
 
-  $all = array_merge($tokens['global']['default'], $tokens[$type][$id]);
+  $all = $tokens['global'][$cid];
+  if ($type != 'global') {
+    // Avoid using array_merge() if only global tokens were requested.
+    $all = array_merge($all, $tokens[$type][$cid]);
+  }
 
   $result = new stdClass();
   $result->tokens = array_keys($all);
diff --git a/token.test b/token.test
index 38cbf82..cc9dec8 100644
--- a/token.test
+++ b/token.test
@@ -21,7 +21,7 @@ class TokenTestHelper extends DrupalWebTestCase {
   }
 
   function assertTokens($type, $object, array $tokens, array $options = array()) {
-    $values = token_get_values($type, $object, TRUE, $options);
+    $values = token_get_values($type, $object, FALSE, $options);
     $values = array_combine($values->tokens, $values->values);
     foreach ($tokens as $token => $expected) {
       if (!isset($expected)) {
@@ -216,6 +216,24 @@ class TokenUnitTestCase extends TokenTestHelper {
       $this->assertEqual($output, $expected);
     }
   }
+
+  /**
+   * Test token caching.
+   */
+  function testTokenCaching() {
+    // Run global tokens once so that the cache is primed.
+    $tokens = array(
+      'option-foo' => '',
+    );
+    $this->assertTokens('global', NULL, $tokens);
+
+    // Run global tokens again with different options. This should return a
+    // different value for the [option-foo] token.
+    $tokens = array(
+      'option-foo' => 'bar',
+    );
+    $this->assertTokens('global', NULL, $tokens, array('foo' => 'bar'));
+  }
 }
 
 class TokenNodeTestCase extends TokenTestHelper {
