diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 369fdfc..fff194c 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Utility\VariableCache;
 use Symfony\Component\ClassLoader\UniversalClassLoader;
 use Symfony\Component\ClassLoader\ApcUniversalClassLoader;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -800,27 +801,11 @@ function drupal_get_filename($type, $name, $filename = NULL) {
  * configuration file.
  */
 function variable_initialize($conf = array()) {
-  // NOTE: caching the variables improves performance by 20% when serving
-  // cached pages.
-  if ($cached = cache('bootstrap')->get('variables')) {
-    $variables = $cached->data;
-  }
-  else {
-    // Cache miss. Avoid a stampede.
-    $name = 'variable_init';
-    if (!lock_acquire($name, 1)) {
-      // Another request is building the variable cache.
-      // Wait, then re-run this function.
-      lock_wait($name);
-      return variable_initialize($conf);
-    }
-    else {
-      // Proceed with variable rebuild.
-      $variables = array_map('unserialize', db_query('SELECT name, value FROM {variable}')->fetchAllKeyed());
-      cache('bootstrap')->set('variables', $variables);
-      lock_release($name);
-    }
-  }
+  $cache = &drupal_static('variables');
+  $cache = new VariableCache('variables', 'cache_bootstrap');
+  // Copy the cached variables, free memory in the cache array by setting it
+  // to empty.
+  $variables = $cache->replaceStorage(array());
 
   foreach ($conf as $name => $value) {
     $variables[$name] = $value;
@@ -850,7 +835,15 @@ function variable_initialize($conf = array()) {
 function variable_get($name, $default = NULL) {
   global $conf;
 
-  return isset($conf[$name]) ? $conf[$name] : $default;
+  if (isset($conf[$name])) {
+    return $conf[$name];
+  }
+  $cache = &drupal_static('variables');
+  if (isset($cache) && isset($cache[$name])) {
+    $conf[$name] = $cache[$name];
+    return $conf[$name];
+  }
+  return $default;
 }
 
 /**
@@ -874,9 +867,22 @@ function variable_set($name, $value) {
 
   db_merge('variable')->key(array('name' => $name))->fields(array('value' => serialize($value)))->execute();
 
-  cache('bootstrap')->delete('variables');
-
   $conf[$name] = $value;
+
+  $cache = &drupal_static('variables');
+  if (is_object($cache)) {
+    // Write through to the variable cache.
+    $replace = array();
+    if ($cached = cache('bootstrap')->get('variables')) {
+      $cached->data[$name] = $value;
+      cache('bootstrap')->set('variables', $cached->data);
+      $replace = $cached->data;
+    }
+    $cache->replaceStorage($replace);
+  }
+  else {
+    cache('bootstrap')->delete('variables');
+  }
 }
 
 /**
@@ -898,9 +904,16 @@ function variable_del($name) {
   db_delete('variable')
     ->condition('name', $name)
     ->execute();
-  cache('bootstrap')->delete('variables');
 
   unset($conf[$name]);
+
+  $cache = &drupal_static('variables');
+  if (is_object($cache)) {
+    unset($cache[$name]);
+  }
+  else {
+    cache('bootstrap')->delete('variables');
+  }
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Utility/CacheArray.php b/core/lib/Drupal/Core/Utility/CacheArray.php
index bba2bc4..97ea946 100644
--- a/core/lib/Drupal/Core/Utility/CacheArray.php
+++ b/core/lib/Drupal/Core/Utility/CacheArray.php
@@ -38,7 +38,7 @@ use ArrayAccess;
  * otherwise, will fail silently. So $var = &$object['foo'] will not throw an
  * error, and $var will be populated with the contents of $object['foo'], but
  * that data will be passed by value, not reference. For more information on
- * the PHP limitation, see the note in the official PHP documentation atá
+ * the PHP limitation, see the note in the official PHP documentation at
  * http://php.net/manual/arrayaccess.offsetget.php on
  * ArrayAccess::offsetGet().
  *
diff --git a/core/lib/Drupal/Core/Utility/VariableCache.php b/core/lib/Drupal/Core/Utility/VariableCache.php
new file mode 100644
index 0000000..773b909
--- /dev/null
+++ b/core/lib/Drupal/Core/Utility/VariableCache.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @file
+ * Definition of Drupal\Core\Utility\VariableCache.
+ */
+
+namespace Drupal\Core\Utility;
+
+use Drupal\Core\Utility\CacheArray;
+
+/*
+ * Extends CacheArray to allow for cumulative caching of variables.
+ */
+class VariableCache extends CacheArray {
+  /**
+   * Overrides CacheArray::resolveCacheMiss().
+   */
+  protected function resolveCacheMiss($offset) {
+    $result = db_query('SELECT value FROM {variable} WHERE name = :name', array(':name' => $offset))->fetchField();
+    $value = $result ? unserialize($result) : NULL;
+    $this->storage[$offset] = $value;
+    $this->persist($offset);
+    return $value;
+  }
+
+  /**
+   * Replaces the storage variable with a new array.
+   *
+   * @param array $array
+   *   An associative array containing the new storage.
+   *
+   * @return
+   *   The value of the storage variable before it was replaced.
+   */
+  public function replaceStorage(array $array) {
+    $return = $this->storage;
+
+    // Unpersist the previous storage entries.
+    foreach ($this->keysToPersist as $key => $value) {
+      $this->persist($key, FALSE);
+    }
+
+    // Replace the storage and keys to persist with the new data.
+    $this->storage = $array;
+    $this->keysToPersist = array();
+    foreach ($array as $key => $value) {
+      $this->persist($key);
+    }
+
+    return $return;
+  }
+
+  /**
+   * Overrides CacheArray::offsetUnset().
+   *
+   * Instead of unsetting the key, replace it with NULL and persist it.
+   *
+   * @todo Document why.
+   */
+  public function offsetUnset($offset) {
+    $this->storage[$offset] = NULL;
+    $this->persist($offset);
+  }
+}
+
diff --git a/core/modules/system/tests/batch.test b/core/modules/system/tests/batch.test
index 1e9b31b..3d10b20 100644
--- a/core/modules/system/tests/batch.test
+++ b/core/modules/system/tests/batch.test
@@ -83,6 +83,7 @@ class BatchProcessingTestCase extends DrupalWebTestCase {
     $this->assertBatchMessages($this->_resultMessages('batch_1'), t('Batch for step 1 performed successfully.'));
     $this->assertEqual(batch_test_stack(), $this->_resultStack('batch_1'), t('Execution order was correct.'));
     $this->assertText('step 2', t('Form is displayed in step 2.'));
+    drupal_static_reset('variables');
 
     // Second step triggers batch 2.
     $this->drupalPost(NULL, array(), 'Submit');
diff --git a/core/modules/system/tests/upgrade/upgrade.test b/core/modules/system/tests/upgrade/upgrade.test
index e4045be..725c527 100644
--- a/core/modules/system/tests/upgrade/upgrade.test
+++ b/core/modules/system/tests/upgrade/upgrade.test
@@ -56,6 +56,10 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
 
     $this->loadedModules = module_list();
 
+    // Reset all statics and variables to perform tests in a clean environment.
+    $conf = array();
+    drupal_static_reset();
+
     // Generate a temporary prefixed database to ensure that tests have a clean starting point.
     $this->databasePrefix = 'simpletest' . mt_rand(1000, 1000000);
     db_update('simpletest_test_id')
@@ -102,9 +106,6 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
     ini_set('log_errors', 1);
     ini_set('error_log', $public_files_directory . '/error.log');
 
-    // Reset all statics and variables to perform tests in a clean environment.
-    $conf = array();
-
     // Load the database from the portable PHP dump.
     // The files can be gzipped.
     foreach ($this->databaseDumpFiles as $file) {
