diff --git a/core/lib/Drupal/Core/Cache/BackendChain.php b/core/lib/Drupal/Core/Cache/BackendChain.php
index 4d8ca05..2ea1118 100644
--- a/core/lib/Drupal/Core/Cache/BackendChain.php
+++ b/core/lib/Drupal/Core/Cache/BackendChain.php
@@ -132,6 +132,15 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items) {
+    foreach ($this->backends as $backend) {
+      $backend->setMultiple($items);
+    }
+  }
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::delete().
    */
   public function delete($cid) {
diff --git a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
index f0e9882..f604980 100644
--- a/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
+++ b/core/lib/Drupal/Core/Cache/CacheBackendInterface.php
@@ -96,6 +96,26 @@ public function getMultiple(&$cids, $allow_invalid = FALSE);
   public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array());
 
   /**
+   * Store multiple items in the persistent cache.
+   *
+   * @param array $items
+   *   An array of cache items, keyed by cid. In the form:
+   *   @code
+   *   $items = array(
+   *     $cid => array(
+   *       // Required, will be automatically serialized if not a string.
+   *       'data' => $data,
+   *       // Optional, defaults to CacheBackendInterface::CACHE_PERMANENT.
+   *       'expire' => CacheBackendInterface::CACHE_PERMANENT,
+   *       // (optional) The cache tags for this item, see CacheBackendInterface::set().
+   *       'tags' => array(),
+   *     ),
+   *   );
+   *   @endcode
+   */
+  public function setMultiple(array $items);
+
+  /**
    * Deletes an item from the cache.
    *
    * If the cache item is being deleted because it is no longer "fresh", you may
diff --git a/core/lib/Drupal/Core/Cache/DatabaseBackend.php b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
index 4c431dc..016fbdd 100644
--- a/core/lib/Drupal/Core/Cache/DatabaseBackend.php
+++ b/core/lib/Drupal/Core/Cache/DatabaseBackend.php
@@ -202,6 +202,76 @@ protected function doSet($cid, $data, $expire, $tags) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items) {
+    $deleted_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::deletedTags', array());
+    $invalidated_tags = &drupal_static('Drupal\Core\Cache\DatabaseBackend::invalidatedTags', array());
+
+    // Use a transaction so that the database can write the changes in a single
+    // commit.
+    $transaction = $this->connection->startTransaction();
+
+    try {
+      $this->deleteMultiple(array_keys($items));
+
+      $query = $this->connection
+        ->insert($this->bin)
+        ->fields(array('cid', 'data', 'expire', 'created', 'serialized', 'tags', 'checksum_invalidations', 'checksum_deletions'));
+
+      foreach ($items as $cid => $item) {
+        $item += array(
+          'expire' => CacheBackendInterface::CACHE_PERMANENT,
+          'tags' => array(),
+        );
+
+        $flat_tags = $this->flattenTags($item['tags']);
+
+        // Remove tags that were already deleted or invalidated during this request
+        // from the static caches so that another deletion or invalidation can
+        // occur.
+        foreach ($flat_tags as $tag) {
+          if (isset($deleted_tags[$tag])) {
+            unset($deleted_tags[$tag]);
+          }
+          if (isset($invalidated_tags[$tag])) {
+            unset($invalidated_tags[$tag]);
+          }
+        }
+
+        $checksum = $this->checksumTags($flat_tags);
+
+        $fields = array(
+          'cid' => $cid,
+          'expire' => $item['expire'],
+          'created' => REQUEST_TIME,
+          'tags' => implode(' ', $flat_tags),
+          'checksum_invalidations' => $checksum['invalidations'],
+          'checksum_deletions' => $checksum['deletions'],
+        );
+
+        if (!is_string($item['data'])) {
+          $fields['data'] = serialize($item['data']);
+          $fields['serialized'] = 1;
+        }
+        else {
+          $fields['data'] = $item['data'];
+          $fields['serialized'] = 0;
+        }
+
+        $query->values($fields);
+      }
+
+      $query->execute();
+    }
+    catch (\Exception $e) {
+      $transaction->rollback();
+      // @todo Log something here or just re throw?
+      throw $e;
+    }
+  }
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::delete().
    */
   public function delete($cid) {
diff --git a/core/lib/Drupal/Core/Cache/MemoryBackend.php b/core/lib/Drupal/Core/Cache/MemoryBackend.php
index c4e16f7..fb9e29c 100644
--- a/core/lib/Drupal/Core/Cache/MemoryBackend.php
+++ b/core/lib/Drupal/Core/Cache/MemoryBackend.php
@@ -107,6 +107,15 @@ public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items = array()) {
+    foreach ($items as $cid => $item) {
+      $this->set($cid, $item['data'], isset($item['expire']) ? $item['expire'] : CacheBackendInterface::CACHE_PERMANENT, isset($item['tags']) ? $item['tags'] : array());
+    }
+  }
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::delete().
    */
   public function delete($cid) {
@@ -196,11 +205,11 @@ protected function flattenTags(array $tags) {
     foreach ($tags as $namespace => $values) {
       if (is_array($values)) {
         foreach ($values as $value) {
-          $flat_tags["$namespace:$value"] = "$namespace:$value";
+          $flat_tags[] = "$namespace:$value";
         }
       }
       else {
-        $flat_tags["$namespace:$values"] = "$namespace:$values";
+        $flat_tags[] = "$namespace:$values";
       }
     }
     return $flat_tags;
diff --git a/core/lib/Drupal/Core/Cache/NullBackend.php b/core/lib/Drupal/Core/Cache/NullBackend.php
index 94a426d..2d5664d 100644
--- a/core/lib/Drupal/Core/Cache/NullBackend.php
+++ b/core/lib/Drupal/Core/Cache/NullBackend.php
@@ -50,6 +50,11 @@ public function getMultiple(&$cids, $allow_invalid = FALSE) {
   public function set($cid, $data, $expire = Cache::PERMANENT, array $tags = array()) {}
 
   /**
+   * {@inheritdoc}
+   */
+  public function setMultiple(array $items = array()) {}
+
+  /**
    * Implements Drupal\Core\Cache\CacheBackendInterface::delete().
    */
   public function delete($cid) {}
diff --git a/core/lib/Drupal/Core/Config/CachedStorage.php b/core/lib/Drupal/Core/Config/CachedStorage.php
index 1ff4046..6394305 100644
--- a/core/lib/Drupal/Core/Config/CachedStorage.php
+++ b/core/lib/Drupal/Core/Config/CachedStorage.php
@@ -93,9 +93,12 @@ public function readMultiple(array $names) {
       $list = $this->storage->readMultiple($names);
       // Cache configuration objects that were loaded from the storage, cache
       // missing configuration objects as an explicit FALSE.
+      $items = array();
       foreach ($names as $name) {
-        $this->cache->set($name, isset($list[$name]) ? $list[$name] : FALSE);
+        $items[$name] = array('data' => isset($list[$name]) ? $list[$name] : FALSE);
       }
+
+      $this->cache->setMultiple($items);
     }
 
     // Add the configuration objects from the cache to the list.
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
index d4066b0..feb5471 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php
@@ -237,7 +237,7 @@ public function testBlockViewBuilderAlter() {
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
     $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache');
-    $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
+    $this->assertIdentical($cache_entry->tags, $expected_flattened_tags); //, 'The block render element has been cached with the expected cache tags.');
     $this->container->get('cache.render')->delete($cid);
 
     // Advanced: cached block, but an alter hook adds an additional cache tag.
@@ -251,7 +251,7 @@ public function testBlockViewBuilderAlter() {
     $cache_entry = $this->container->get('cache.render')->get($cid);
     $this->assertTrue($cache_entry, 'The block render element has been cached with the expected cache ID.');
     $expected_flattened_tags = array('content:1', 'block_view:1', 'block:test_block', 'theme:stark', 'block_plugin:test_cache', $alter_add_tag . ':1');
-    $this->assertIdentical($cache_entry->tags, array_combine($expected_flattened_tags, $expected_flattened_tags)); //, 'The block render element has been cached with the expected cache tags.');
+    $this->assertIdentical($cache_entry->tags, $expected_flattened_tags); //, 'The block render element has been cached with the expected cache tags.');
     $this->container->get('cache.render')->delete($cid);
 
     // Advanced: cached block, but an alter hook adds a #pre_render callback to
diff --git a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
index e558816..da28fc4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Cache/GenericCacheBackendUnitTestBase.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Cache;
 
 use Drupal\Core\Cache\Cache;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
@@ -295,6 +296,43 @@ public function testGetMultiple() {
     $this->assertFalse(in_array('test19', $cids), "Added cache id test19 is not in cids array.");
   }
 
+   /**
+   * Tests \Drupal\Core\Cache\CacheBackendInterface::setMultiple().
+   */
+  public function testSetMultiple() {
+    $backend = $this->getCacheBackend();
+
+    $future_expiration = REQUEST_TIME + 100;
+
+    // Set multiple testing keys.
+    $backend->set('cid_1', 'Some other value');
+    $items = array(
+      'cid_1' => array('data' => 1),
+      'cid_2' => array('data' => 2),
+      'cid_3' => array('data' => array(1, 2)),
+      'cid_4' => array('data' => 1, 'expire' => $future_expiration),
+      'cid_5' => array('data' => 1, 'tags' => array('test' => array('a', 'b'))),
+    );
+    $backend->setMultiple($items);
+    $cids = array_keys($items);
+    $cached = $backend->getMultiple($cids);
+
+    $this->assertEqual($cached['cid_1']->data, $items['cid_1']['data'], 'Over-written cache item set correctly.');
+    $this->assertEqual($cached['cid_1']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
+
+    $this->assertEqual($cached['cid_2']->data, $items['cid_2']['data'], 'New cache item set correctly.');
+    $this->assertEqual($cached['cid_2']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
+
+    $this->assertEqual($cached['cid_3']->data, $items['cid_3']['data'], 'New cache item with serialized data set correctly.');
+    $this->assertEqual($cached['cid_3']->expire, CacheBackendInterface::CACHE_PERMANENT, 'Cache expiration defaults to permanent.');
+
+    $this->assertEqual($cached['cid_4']->data, $items['cid_4']['data'], 'New cache item set correctly.');
+    $this->assertEqual($cached['cid_4']->expire, $future_expiration, 'Cache expiration has been correctly set.');
+
+    $this->assertEqual($cached['cid_5']->data, $items['cid_5']['data'], 'New cache item set correctly.');
+    $this->assertEqual($cached['cid_5']->tags, array('test:a', 'test:b'));
+  }
+
   /**
    * Tests Drupal\Core\Cache\CacheBackendInterface::isEmpty().
    */
