diff --git a/core/core.services.yml b/core/core.services.yml
index 2bc5edc..9a5a6bb 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -1193,7 +1193,7 @@ services:
     arguments: ['@app.root', '@theme_handler', '@state', '@module_handler']
   theme.registry:
     class: Drupal\Core\Theme\Registry
-    arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization']
+    arguments: ['@app.root', '@cache.default', '@lock', '@module_handler', '@theme_handler', '@theme.initialization', '@request_stack']
     tags:
       - { name: needs_destruction }
     calls:
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
index bb89707..2c2e6bd 100644
--- a/core/lib/Drupal/Core/Theme/Registry.php
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -14,6 +14,7 @@
 use Drupal\Core\Extension\ThemeHandlerInterface;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\Utility\ThemeRegistry;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Defines the theme registry service.
@@ -138,6 +139,13 @@ class Registry implements DestructableInterface {
   protected $themeManager;
 
   /**
+   * The related request stack.
+   *
+   * @var \Symfony\Component\HttpFoundation\RequestStack
+   */
+  protected $requestStack;
+
+  /**
    * Constructs a \Drupal\Core\Theme\Registry object.
    *
    * @param string $root
@@ -152,10 +160,12 @@ class Registry implements DestructableInterface {
    *   The theme handler.
    * @param \Drupal\Core\Theme\ThemeInitializationInterface $theme_initialization
    *   The theme initialization.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
    * @param string $theme_name
    *   (optional) The name of the theme for which to construct the registry.
    */
-  public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, $theme_name = NULL) {
+  public function __construct($root, CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, ThemeHandlerInterface $theme_handler, ThemeInitializationInterface $theme_initialization, RequestStack $request_stack, $theme_name = NULL) {
     $this->root = $root;
     $this->cache = $cache;
     $this->lock = $lock;
@@ -163,6 +173,7 @@ public function __construct($root, CacheBackendInterface $cache, LockBackendInte
     $this->themeName = $theme_name;
     $this->themeHandler = $theme_handler;
     $this->themeInitialization = $theme_initialization;
+    $this->requestStack = $request_stack;
   }
 
   /**
@@ -236,7 +247,7 @@ public function get() {
   public function getRuntime() {
     $this->init($this->themeName);
     if (!isset($this->runtimeRegistry)) {
-      $this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, array('theme_registry'), $this->moduleHandler->isLoaded());
+      $this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->getName(), $this->cache, $this->lock, $this->requestStack, array('theme_registry'), $this->moduleHandler->isLoaded());
     }
     return $this->runtimeRegistry;
   }
diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
index edc0089..17dcea2 100644
--- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php
+++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Cache\CacheCollector;
 use Drupal\Core\DestructableInterface;
 use Drupal\Core\Lock\LockBackendInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Builds the run-time theme registry.
@@ -29,15 +30,33 @@ class ThemeRegistry extends CacheCollector implements DestructableInterface {
    * This is only allowed if all modules and the request method is GET. _theme()
    * should be very rarely called on POST requests and this avoids polluting
    * the runtime cache.
+   *
+   * @var bool
    */
-  protected $persistable;
+  protected $persistable = NULL;
 
   /**
    * The complete theme registry array.
+   *
+   * @var array
    */
   protected $completeRegistry;
 
   /**
+   * Whether all modules have already been loaded.
+   *
+   * @var bool
+   */
+  protected $modulesLoaded;
+
+  /**
+   * The theme object representing the active theme for this registry.
+   *
+   * @var \Drupal\Core\Theme\ActiveTheme
+   */
+  protected $theme;
+
+  /**
    * Constructs a ThemeRegistry object.
    *
    * @param string $cid
@@ -46,37 +65,61 @@ class ThemeRegistry extends CacheCollector implements DestructableInterface {
    *   The cache backend.
    * @param \Drupal\Core\Lock\LockBackendInterface $lock
    *   The lock backend.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
    * @param array $tags
    *   (optional) The tags to specify for the cache item.
    * @param bool $modules_loaded
    *   Whether all modules have already been loaded.
    */
-  function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, $tags = array(), $modules_loaded = FALSE) {
-    $this->cid = $cid;
+  function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, RequestStack $request_stack, $tags = array(), $modules_loaded = FALSE) {
     $this->cache = $cache;
     $this->lock = $lock;
     $this->tags = $tags;
-    $this->persistable = $modules_loaded && \Drupal::hasRequest() && \Drupal::request()->isMethod('GET');
-
-     // @todo: Implement lazyload.
-    $this->cacheLoaded = TRUE;
+    $this->cid = $cid;
+    $this->modulesLoaded = $modules_loaded;
+    $this->requestStack = $request_stack;
+  }
 
-    if ($this->persistable && $cached = $this->cache->get($this->cid)) {
-      $this->storage = $cached->data;
+  /**
+   * Gets the persistable.
+   *
+   * @return bool
+   */
+  protected function getPersistable() {
+    if ($this->persistable === NULL) {
+      $this->persistable = $this->modulesLoaded && $this->requestStack->getCurrentRequest() !== NULL && $this->requestStack->getCurrentRequest()->isMethod('GET');
     }
-    else {
-      // If there is no runtime cache stored, fetch the full theme registry,
-      // but then initialize each value to NULL. This allows offsetExists()
-      // to function correctly on non-registered theme hooks without triggering
-      // a call to resolveCacheMiss().
-      $this->storage = $this->initializeRegistry();
-      foreach (array_keys($this->storage) as $key) {
-        $this->persist($key);
+
+    return $this->persistable;
+  }
+
+  /**
+   * Gets the storage.
+   *
+   * @return array
+   */
+  protected function getStorage() {
+    if (!$this->storage) {
+      if ($this->getPersistable() && $cached = $this->cache->get($this->getCid())) {
+        $this->storage = $cached->data;
+      }
+      else {
+        // If there is no runtime cache stored, fetch the full theme registry,
+        // but then initialize each value to NULL. This allows offsetExists()
+        // to function correctly on non-registered theme hooks without
+        // triggering a call to resolveCacheMiss().
+        $this->storage = $this->initializeRegistry();
+        foreach (array_keys($this->storage) as $key) {
+          $this->persist($key);
+        }
+        // RegistryTest::testRaceCondition() ensures that the cache entry is
+        // written when the theme registry is actually used.
+        $this->updateCache();
       }
-      // RegistryTest::testRaceCondition() ensures that the cache entry is
-      // written on the initial construction of the theme registry.
-      $this->updateCache();
     }
+
+    return $this->storage;
   }
 
   /**
@@ -101,7 +144,7 @@ public function has($key) {
     // are not registered, just check the existence of the key in the registry.
     // Use array_key_exists() here since a NULL value indicates that the theme
     // hook exists but has not yet been requested.
-    return array_key_exists($key, $this->storage);
+    return array_key_exists($key, $this->getStorage());
   }
 
   /**
@@ -111,10 +154,11 @@ public function get($key) {
     // If the offset is set but empty, it is a registered theme hook that has
     // not yet been requested. Offsets that do not exist at all were not
     // registered in hook_theme().
-    if (isset($this->storage[$key])) {
-      return $this->storage[$key];
+    $storage = $this->getStorage();
+    if (isset($storage[$key])) {
+      return $storage[$key];
     }
-    elseif (array_key_exists($key, $this->storage)) {
+    elseif (array_key_exists($key, $storage)) {
       return $this->resolveCacheMiss($key);
     }
   }
@@ -126,34 +170,35 @@ public function resolveCacheMiss($key) {
     if (!isset($this->completeRegistry)) {
       $this->completeRegistry = \Drupal::service('theme.registry')->get();
     }
-    $this->storage[$key] = $this->completeRegistry[$key];
-    if ($this->persistable) {
+    $storage = $this->getStorage();
+    $storage[$key] = $this->completeRegistry[$key];
+    if ($this->getPersistable()) {
       $this->persist($key);
     }
-    return $this->storage[$key];
+    return $storage[$key];
   }
 
   /**
    * {@inheritdoc}
    */
   protected function updateCache($lock = TRUE) {
-    if (!$this->persistable) {
+    if (!$this->getPersistable()) {
       return;
     }
     // @todo: Is the custom implementation necessary?
     $data = array();
     foreach ($this->keysToPersist as $offset => $persist) {
       if ($persist) {
-        $data[$offset] = $this->storage[$offset];
+        $data[$offset] = $this->getStorage()[$offset];
       }
     }
     if (empty($data)) {
       return;
     }
 
-    $lock_name = $this->cid . ':' . __CLASS__;
+    $lock_name = $this->getCid() . ':' . __CLASS__;
     if (!$lock || $this->lock->acquire($lock_name)) {
-      if ($cached = $this->cache->get($this->cid)) {
+      if ($cached = $this->cache->get($this->getCid())) {
         // Use array merge instead of union so that filled in values in $data
         // overwrite empty values in the current cache.
         $data = array_merge($cached->data, $data);
@@ -162,7 +207,7 @@ protected function updateCache($lock = TRUE) {
         $registry = $this->initializeRegistry();
         $data = array_merge($registry, $data);
       }
-      $this->cache->set($this->cid, $data, Cache::PERMANENT, $this->tags);
+      $this->cache->set($this->getCid(), $data, Cache::PERMANENT, $this->tags);
       if ($lock) {
         $this->lock->release($lock_name);
       }
diff --git a/core/modules/system/src/Tests/Theme/RegistryTest.php b/core/modules/system/src/Tests/Theme/RegistryTest.php
index 8872f10..777f33d 100644
--- a/core/modules/system/src/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/src/Tests/Theme/RegistryTest.php
@@ -41,11 +41,13 @@ function testRaceCondition() {
     $cache = \Drupal::cache();
     $lock_backend = \Drupal::lock();
     $registry = new ThemeRegistry($cid, $cache, $lock_backend, array('theme_registry'), $this->container->get('module_handler')->isLoaded());
-
-    $this->assertTrue(\Drupal::cache()->get($cid), 'Cache entry was created.');
+    // Test that lazy loading works.
+    $this->assertFalse(\Drupal::cache()->get($cid), 'Cache entry was not created by default.');
 
     // Trigger a cache miss for an offset.
     $this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry.');
+    // After registry has been actually used we should have a cache entry.
+    $this->assertTrue(\Drupal::cache()->get($cid), 'Cache entry was created.');
     // This will cause the ThemeRegistry class to write an updated version of
     // the cache entry when it is destroyed, usually at the end of the request.
     // Before that happens, manually delete the cache entry we created earlier
