diff --git a/core/core.services.yml b/core/core.services.yml
index 4d2a29a9b47091bbfedb6260cfe88dc8c327e4f3..d3416063012d0036013b32d8b706e61e9abb46f6 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -641,6 +641,11 @@ services:
     class: Zend\Feed\Writer\Extension\Threading\Renderer\Entry
   feed.writer.wellformedwebrendererentry:
     class: Zend\Feed\Writer\Extension\WellFormedWeb\Renderer\Entry
+  theme.registry:
+    class: Drupal\Core\Theme\Registry
+    arguments: ['@cache.cache', '@lock', '@module_handler']
+    tags:
+      - { name: needs_destruction }
   authentication:
     class: Drupal\Core\Authentication\AuthenticationManager
   authentication.cookie:
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 9159f71cc107463532a457da38e8758484fc0951..36b6b1418dd6078bfaf0d28c8d7ca1f22421603b 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -410,6 +410,13 @@ function install_begin_request(&$install_state) {
     // implementation here.
     $container->register('lock', 'Drupal\Core\Lock\NullLockBackend');
 
+    $container
+      ->register('theme.registry', 'Drupal\Core\Theme\Registry')
+      ->addArgument(new Reference('cache.cache'))
+      ->addArgument(new Reference('lock'))
+      ->addArgument(new Reference('module_handler'))
+      ->addTag('needs_destruction');
+
     // Register a module handler for managing enabled modules.
     $container
       ->register('module_handler', 'Drupal\Core\Extension\ModuleHandler');
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 6ce5c6338bc0c99c24e7dc8f4c12e726e86d05f1..069b4978b365c58e6334587e9e3b85420a07b97a 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -10,13 +10,11 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Component\Utility\Url;
-use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Extension\ExtensionNameLengthException;
 use Drupal\Core\Template\Attribute;
 use Drupal\Core\Template\RenderWrapper;
-use Drupal\Core\Utility\ThemeRegistry;
 use Drupal\Core\Theme\ThemeSettings;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\MapArray;
@@ -144,10 +142,8 @@ function drupal_theme_initialize() {
  *    the same information as the $theme object. It should be in
  *    'oldest first' order, meaning the top level of the chain will
  *    be first.
- * @param $registry_callback
- *   The callback to invoke to set the theme registry.
  */
-function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callback = '_theme_load_registry') {
+function _drupal_theme_initialize($theme, $base_theme = array()) {
   global $theme_info, $base_theme_info, $theme_engine, $theme_path;
   $theme_info = $theme;
   $base_theme_info = $base_theme;
@@ -276,10 +272,6 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
 
   // Always include Twig as the default theme engine.
   include_once DRUPAL_ROOT . '/core/themes/engines/twig/twig.engine';
-
-  if (isset($registry_callback)) {
-    _theme_registry_callback($registry_callback, array($theme, $base_theme, $theme_engine));
-  }
 }
 
 /**
@@ -300,97 +292,15 @@ function _drupal_theme_initialize($theme, $base_theme = array(), $registry_callb
  *   Drupal\Core\Utility\ThemeRegistry class.
  */
 function theme_get_registry($complete = TRUE) {
-  // Use the advanced drupal_static() pattern, since this is called very often.
-  static $drupal_static_fast;
-  if (!isset($drupal_static_fast)) {
-    $drupal_static_fast['registry'] = &drupal_static('theme_get_registry');
-  }
-  $theme_registry = &$drupal_static_fast['registry'];
-
-  // Initialize the theme, if this is called early in the bootstrap, or after
-  // static variables have been reset.
-  if (!is_array($theme_registry)) {
-    drupal_theme_initialize();
-    $theme_registry = array();
-  }
-
-  $key = (int) $complete;
-
-  if (!isset($theme_registry[$key])) {
-    list($callback, $arguments) = _theme_registry_callback();
-    if (!$complete) {
-      $arguments[] = FALSE;
-    }
-    $theme_registry[$key] = call_user_func_array($callback, $arguments);
-  }
-
-  return $theme_registry[$key];
-}
-
-/**
- * Sets the callback that will be used by theme_get_registry().
- *
- * @param $callback
- *   The name of the callback function.
- * @param $arguments
- *   The arguments to pass to the function.
- */
-function _theme_registry_callback($callback = NULL, array $arguments = array()) {
-  static $stored;
-  if (isset($callback)) {
-    $stored = array($callback, $arguments);
-  }
-  return $stored;
-}
-
-/**
- * Gets the theme_registry cache; if it doesn't exist, builds it.
- *
- * @param $theme
- *   The loaded $theme object as returned by list_themes().
- * @param $base_theme
- *   An array of loaded $theme objects representing the ancestor themes in
- *   oldest first order.
- * @param $theme_engine
- *   The name of the theme engine.
- * @param $complete
- *   Whether to load the complete theme registry or an instance of the
- *   Drupal\Core\Utility\ThemeRegistry class.
- *
- * @return
- *   The theme registry array, or an instance of the
- *   Drupal\Core\Utility\ThemeRegistry class.
- */
-function _theme_load_registry($theme, $base_theme = NULL, $theme_engine = NULL, $complete = TRUE) {
+  $theme_registry = \Drupal::service('theme.registry');
   if ($complete) {
-    // Check the theme registry cache; if it exists, use it.
-    $cached = cache()->get("theme_registry:$theme->name");
-    if (isset($cached->data)) {
-      $registry = $cached->data;
-    }
-    else {
-      // If not, build one and cache it.
-      $registry = _theme_build_registry($theme, $base_theme, $theme_engine);
-      // Only persist this registry if all modules are loaded. This assures a
-      // complete set of theme hooks.
-      if (\Drupal::moduleHandler()->isLoaded()) {
-        _theme_save_registry($theme, $registry);
-      }
-    }
-    return $registry;
+    return $theme_registry->get();
   }
   else {
-    return new ThemeRegistry('theme_registry:runtime:' . $theme->name, 'cache', array('theme_registry' => TRUE), \Drupal::moduleHandler()->isLoaded());
+    return $theme_registry->getRuntime();
   }
 }
 
-/**
- * Writes the theme_registry cache into the database.
- */
-function _theme_save_registry($theme, $registry) {
-  cache()->set("theme_registry:$theme->name", $registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
-}
-
 /**
  * Forces the system to rebuild the theme registry.
  *
@@ -398,256 +308,7 @@ function _theme_save_registry($theme, $registry) {
  * a dynamic system needs to add more theme hooks.
  */
 function drupal_theme_rebuild() {
-  drupal_static_reset('theme_get_registry');
-  cache()->invalidateTags(array('theme_registry' => TRUE));
-}
-
-/**
- * Process a single implementation of hook_theme().
- *
- * @param $cache
- *   The theme registry that will eventually be cached; It is an associative
- *   array keyed by theme hooks, whose values are associative arrays describing
- *   the hook:
- *   - 'type': The passed-in $type.
- *   - 'theme path': The passed-in $path.
- *   - 'function': The name of the function generating output for this theme
- *     hook. Either defined explicitly in hook_theme() or, if neither
- *     'function' nor 'template' is defined, then the default theme function
- *     name is used. The default theme function name is the theme hook prefixed
- *     by either 'theme_' for modules or '$name_' for everything else. If
- *     'function' is defined, 'template' is not used.
- *   - 'template': The filename of the template generating output for this
- *     theme hook. The template is in the directory defined by the 'path' key of
- *     hook_theme() or defaults to "$path/templates".
- *   - 'variables': The variables for this theme hook as defined in
- *     hook_theme(). If there is more than one implementation and 'variables'
- *     is not specified in a later one, then the previous definition is kept.
- *   - 'render element': The renderable element for this theme hook as defined
- *     in hook_theme(). If there is more than one implementation and
- *     'render element' is not specified in a later one, then the previous
- *     definition is kept.
- *   - 'preprocess functions': See theme() for detailed documentation.
- * @param $name
- *   The name of the module, theme engine, base theme engine, theme or base
- *   theme implementing hook_theme().
- * @param $type
- *   One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or
- *   'base_theme'. Unlike regular hooks that can only be implemented by modules,
- *   each of these can implement hook_theme(). _theme_process_registry() is
- *   called in aforementioned order and new entries override older ones. For
- *   example, if a theme hook is both defined by a module and a theme, then the
- *   definition in the theme will be used.
- * @param $theme
- *   The loaded $theme object as returned from list_themes().
- * @param $path
- *   The directory where $name is. For example, modules/system or
- *   themes/bartik.
- *
- * @see theme()
- * @see hook_theme()
- * @see list_themes()
- */
-function _theme_process_registry(&$cache, $name, $type, $theme, $path) {
-  $result = array();
-
-  $hook_defaults = array(
-    'variables' => TRUE,
-    'render element' => TRUE,
-    'pattern' => TRUE,
-    'base hook' => TRUE,
-  );
-
-  $module_list = array_keys(\Drupal::moduleHandler()->getModuleList());
-
-  // Invoke the hook_theme() implementation, preprocess what is returned, and
-  // merge it into $cache.
-  $function = $name . '_theme';
-  if (function_exists($function)) {
-    $result = $function($cache, $type, $theme, $path);
-    foreach ($result as $hook => $info) {
-      // When a theme or engine overrides a module's theme function
-      // $result[$hook] will only contain key/value pairs for information being
-      // overridden.  Pull the rest of the information from what was defined by
-      // an earlier hook.
-
-      // Fill in the type and path of the module, theme, or engine that
-      // implements this theme function.
-      $result[$hook]['type'] = $type;
-      $result[$hook]['theme path'] = $path;
-
-      // If function and file are omitted, default to standard naming
-      // conventions.
-      if (!isset($info['template']) && !isset($info['function'])) {
-        $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook;
-      }
-
-      if (isset($cache[$hook]['includes'])) {
-        $result[$hook]['includes'] = $cache[$hook]['includes'];
-      }
-
-      // If the theme implementation defines a file, then also use the path
-      // that it defined. Otherwise use the default path. This allows
-      // system.module to declare theme functions on behalf of core .include
-      // files.
-      if (isset($info['file'])) {
-        $include_file = isset($info['path']) ? $info['path'] : $path;
-        $include_file .= '/' . $info['file'];
-        include_once DRUPAL_ROOT . '/' . $include_file;
-        $result[$hook]['includes'][] = $include_file;
-      }
-
-      // If the default keys are not set, use the default values registered
-      // by the module.
-      if (isset($cache[$hook])) {
-        $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
-      }
-
-      // The following apply only to theming hooks implemented as templates.
-      if (isset($info['template'])) {
-        // Prepend the current theming path when none is set.
-        if (!isset($info['path'])) {
-          $result[$hook]['template'] = $path . '/templates/' . $info['template'];
-        }
-      }
-
-      // Preprocess variables for all theming hooks, whether the hook is
-      // implemented as a template or as a function. Ensure they are arrays.
-      if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
-        $info['preprocess functions'] = array();
-        $prefixes = array();
-        if ($type == 'module') {
-          // Default variable preprocessor prefix.
-          $prefixes[] = 'template';
-          // Add all modules so they can intervene with their own variable
-          // preprocessors. This allows them to provide variable preprocessors
-          // even if they are not the owner of the current hook.
-          $prefixes = array_merge($prefixes, $module_list);
-        }
-        elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
-          // Theme engines get an extra set that come before the normally
-          // named variable preprocessors.
-          $prefixes[] = $name . '_engine';
-          // The theme engine registers on behalf of the theme using the
-          // theme's name.
-          $prefixes[] = $theme;
-        }
-        else {
-          // This applies when the theme manually registers their own variable
-          // preprocessors.
-          $prefixes[] = $name;
-        }
-        foreach ($prefixes as $prefix) {
-          // Only use non-hook-specific variable preprocessors for theming
-          // hooks implemented as templates. See theme().
-          if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
-            $info['preprocess functions'][] = $prefix . '_preprocess';
-          }
-          if (function_exists($prefix . '_preprocess_' . $hook)) {
-            $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
-          }
-        }
-      }
-      // Check for the override flag and prevent the cached variable
-      // preprocessors from being used. This allows themes or theme engines
-      // to remove variable preprocessors set earlier in the registry build.
-      if (!empty($info['override preprocess functions'])) {
-        // Flag not needed inside the registry.
-        unset($result[$hook]['override preprocess functions']);
-      }
-      elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
-        $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
-      }
-      $result[$hook]['preprocess functions'] = $info['preprocess functions'];
-    }
-
-    // Merge the newly created theme hooks into the existing cache.
-    $cache = $result + $cache;
-  }
-
-  // Let themes have variable preprocessors even if they didn't register a
-  // template.
-  if ($type == 'theme' || $type == 'base_theme') {
-    foreach ($cache as $hook => $info) {
-      // Check only if not registered by the theme or engine.
-      if (empty($result[$hook])) {
-        if (!isset($info['preprocess functions'])) {
-          $cache[$hook]['preprocess functions'] = array();
-        }
-        // Only use non-hook-specific variable preprocessors for theme hooks
-        // implemented as templates. See theme().
-        if (isset($info['template']) && function_exists($name . '_preprocess')) {
-          $cache[$hook]['preprocess functions'][] = $name . '_preprocess';
-        }
-        if (function_exists($name . '_preprocess_' . $hook)) {
-          $cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
-          $cache[$hook]['theme path'] = $path;
-        }
-        // Ensure uniqueness.
-        $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
-      }
-    }
-  }
-}
-
-/**
- * Builds the theme registry cache.
- *
- * @param $theme
- *   The loaded $theme object as returned by list_themes().
- * @param $base_theme
- *   An array of loaded $theme objects representing the ancestor themes in
- *   oldest first order.
- * @param $theme_engine
- *   The name of the theme engine.
- */
-function _theme_build_registry($theme, $base_theme, $theme_engine) {
-  $cache = array();
-  // First, preprocess the theme hooks advertised by modules. This will
-  // serve as the basic registry. Since the list of enabled modules is the same
-  // regardless of the theme used, this is cached in its own entry to save
-  // building it for every theme.
-  if ($cached = cache()->get('theme_registry:build:modules')) {
-    $cache = $cached->data;
-  }
-  else {
-    foreach (\Drupal::moduleHandler()->getImplementations('theme') as $module) {
-      _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
-    }
-    // Only cache this registry if all modules are loaded.
-    if (\Drupal::moduleHandler()->isLoaded()) {
-      cache()->set("theme_registry:build:modules", $cache, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
-    }
-  }
-
-  // Process each base theme.
-  foreach ($base_theme as $base) {
-    // If the base theme uses a theme engine, process its hooks.
-    $base_path = dirname($base->filename);
-    if ($theme_engine) {
-      _theme_process_registry($cache, $theme_engine, 'base_theme_engine', $base->name, $base_path);
-    }
-    _theme_process_registry($cache, $base->name, 'base_theme', $base->name, $base_path);
-  }
-
-  // And then the same thing, but for the theme.
-  if ($theme_engine) {
-    _theme_process_registry($cache, $theme_engine, 'theme_engine', $theme->name, dirname($theme->filename));
-  }
-
-  // Finally, hooks provided by the theme itself.
-  _theme_process_registry($cache, $theme->name, 'theme', $theme->name, dirname($theme->filename));
-
-  // Let modules alter the registry.
-  drupal_alter('theme_registry', $cache);
-
-  // Optimize the registry to not have empty arrays for functions.
-  foreach ($cache as $hook => $info) {
-    if (empty($info['preprocess functions'])) {
-      unset($cache[$hook]['preprocess functions']);
-    }
-  }
-  return $cache;
+  \Drupal::service('theme.registry')->reset();
 }
 
 /**
@@ -913,18 +574,19 @@ function theme($hook, $variables = array()) {
   static $default_attributes;
   // If called before all modules are loaded, we do not necessarily have a full
   // theme registry to work with, and therefore cannot process the theme
-  // request properly. See also _theme_load_registry().
+  // request properly. See also \Drupal\Core\Theme\Registry::get().
   if (!\Drupal::moduleHandler()->isLoaded() && !defined('MAINTENANCE_MODE')) {
     throw new Exception(t('theme() may not be called until all modules are loaded.'));
   }
 
-  $hooks = theme_get_registry(FALSE);
+  /** @var \Drupal\Core\Utility\ThemeRegistry $theme_registry */
+  $theme_registry = \Drupal::service('theme.registry')->getRuntime();
 
   // If an array of hook candidates were passed, use the first one that has an
   // implementation.
   if (is_array($hook)) {
     foreach ($hook as $candidate) {
-      if (isset($hooks[$candidate])) {
+      if ($theme_registry->has($candidate)) {
         break;
       }
     }
@@ -936,16 +598,16 @@ function theme($hook, $variables = array()) {
 
   // If there's no implementation, check for more generic fallbacks. If there's
   // still no implementation, log an error and return an empty string.
-  if (!isset($hooks[$hook])) {
+  if (!$theme_registry->has($hook)) {
     // Iteratively strip everything after the last '__' delimiter, until an
     // implementation is found.
     while ($pos = strrpos($hook, '__')) {
       $hook = substr($hook, 0, $pos);
-      if (isset($hooks[$hook])) {
+      if ($theme_registry->has($hook)) {
         break;
       }
     }
-    if (!isset($hooks[$hook])) {
+    if (!$theme_registry->has($hook)) {
       // Only log a message when not trying theme suggestions ($hook being an
       // array).
       if (!isset($candidate)) {
@@ -958,7 +620,7 @@ function theme($hook, $variables = array()) {
     }
   }
 
-  $info = $hooks[$hook];
+  $info = $theme_registry->get($hook);
   global $theme_path;
   $temp = $theme_path;
   // point path_to_theme() to the currently used theme path:
@@ -1031,8 +693,8 @@ function theme($hook, $variables = array()) {
   // 'node__article' as a suggestion via hook_theme_suggestions_HOOK_alter(),
   // enabling a theme to have an alternate template file for article nodes.
   foreach (array_reverse($suggestions) as $suggestion) {
-    if (isset($hooks[$suggestion])) {
-      $info = $hooks[$suggestion];
+    if ($theme_registry->has($suggestion)) {
+      $info = $theme_registry->get($suggestion);
       break;
     }
   }
@@ -1040,7 +702,7 @@ function theme($hook, $variables = array()) {
   // Invoke the variable preprocessors, if any.
   if (isset($info['base hook'])) {
     $base_hook = $info['base hook'];
-    $base_hook_info = $hooks[$base_hook];
+    $base_hook_info = $theme_registry->get($base_hook);
     // Include files required by the base hook, since its variable preprocessors
     // might reside there.
     if (!empty($base_hook_info['includes'])) {
diff --git a/core/includes/theme.maintenance.inc b/core/includes/theme.maintenance.inc
index b86a25dfa69295345ea09d8052fc1313ad8dabb1..807566cd1567c3cd7988cb21ca07726d7cf98018 100644
--- a/core/includes/theme.maintenance.inc
+++ b/core/includes/theme.maintenance.inc
@@ -91,6 +91,10 @@ function _drupal_maintenance_theme() {
     $ancestor = $themes[$ancestor]->base_theme;
   }
   _drupal_theme_initialize($themes[$theme], array_reverse($base_theme), '_theme_load_offline_registry');
+  _drupal_theme_initialize($themes[$theme], array_reverse($base_theme));
+  // Prime the theme registry.
+  // @todo Remove global theme variables.
+  Drupal::service('theme.registry');
 
   // These CSS files are normally added by system_page_build(), except
   // system.maintenance.css. When the database is inactive, it's not called so
@@ -98,13 +102,6 @@ function _drupal_maintenance_theme() {
   drupal_add_library('system', 'normalize');
 }
 
-/**
- * Builds the registry when the site needs to bypass any database calls.
- */
-function _theme_load_offline_registry($theme, $base_theme = NULL, $theme_engine = NULL) {
-  return _theme_build_registry($theme, $base_theme, $theme_engine);
-}
-
 /**
  * Returns HTML for a list of maintenance tasks to perform.
  *
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 186255db38d3de2486b56b1788a2ef49b3907895..7b8a6e2a438fa8e36c06676433052db63ce328f7 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -458,6 +458,10 @@ function update_prepare_d8_bootstrap() {
   new Settings($settings);
   $kernel = new DrupalKernel('update', drupal_classloader(), FALSE);
   $kernel->boot();
+
+  // Clear the D7 caches, to ensure that for example the theme_registry does not
+  // take part in the upgrade process.
+  Drupal::cache('cache')->deleteAll();
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Theme/Registry.php b/core/lib/Drupal/Core/Theme/Registry.php
new file mode 100644
index 0000000000000000000000000000000000000000..3c5ada7ac0366b8a064d641f1382bcdd7859cba8
--- /dev/null
+++ b/core/lib/Drupal/Core/Theme/Registry.php
@@ -0,0 +1,594 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Theme\Registry.
+ */
+
+namespace Drupal\Core\Theme;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\DestructableInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\Core\Utility\ThemeRegistry;
+
+/**
+ * Defines the theme registry service.
+ *
+ * @todo Replace local $registry variables in methods with $this->registry.
+ */
+class Registry implements DestructableInterface {
+
+  /**
+   * The theme object representing the active theme for this registry.
+   *
+   * @var object
+   */
+  protected $theme;
+
+  /**
+   * An array of base theme objects.
+   *
+   * @var array
+   */
+  protected $baseThemes;
+
+  /**
+   * The name of the theme engine of $theme.
+   *
+   * @var string
+   */
+  protected $engine;
+
+  /**
+   * The lock backend that should be used.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
+  /**
+   * The complete theme registry.
+   *
+   * @var array
+   *   An associative array keyed by theme hook names, whose values are
+   *   associative arrays containing the aggregated hook definition:
+   *   - type: The type of the extension the original theme hook originates
+   *     from; e.g., 'module' for theme hook 'node' of Node module.
+   *   - name: The name of the extension the original theme hook originates
+   *     from; e.g., 'node' for theme hook 'node' of Node module.
+   *   - theme path: The effective path_to_theme() during theme(), available as
+   *     'directory' variable in templates.
+   *       functions, it should point to the respective theme. For templates,
+   *       it should point to the directory that contains the template.
+   *   - includes: (optional) An array of include files to load when the theme
+   *     hook is executed by theme().
+   *   - file: (optional) A filename to add to 'includes', either prefixed with
+   *     the value of 'path', or the path of the extension implementing
+   *     hook_theme().
+   *   In case of a theme base hook, one of the following:
+   *   - variables: An associative array whose keys are variable names and whose
+   *     values are default values of the variables to use for this theme hook.
+   *   - render element: A string denoting the name of the variable name, in
+   *     which the render element for this theme hook is provided.
+   *   In case of a theme template file:
+   *   - path: The path to the template file to use. Defaults to the
+   *     subdirectory 'templates' of the path of the extension implementing
+   *     hook_theme(); e.g., 'core/modules/node/templates' for Node module.
+   *   - template: The basename of the template file to use, without extension
+   *     (as the extension is specific to the theme engine). The template file
+   *     is in the directory defined by 'path'.
+   *   - template_file: A full path and file name to a template file to use.
+   *     Allows any extension to override the effective template file.
+   *   - engine: The theme engine to use for the template file.
+   *   In case of a theme function:
+   *   - function: The function name to call to generate the output.
+   *   For any registered theme hook, including theme hook suggestions:
+   *   - preprocess: An array of theme variable preprocess callbacks to invoke
+   *     before invoking final theme variable processors.
+   *   - process: An array of theme variable process callbacks to invoke
+   *     before invoking the actual theme function or template.
+   */
+  protected $registry;
+
+  /**
+   * The cache backend to use for the complete theme registry data.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cache;
+
+  /**
+   * The module handler to use to load modules.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The incomplete, runtime theme registry.
+   *
+   * @var \Drupal\Core\Utility\ThemeRegistry
+   */
+  protected $runtimeRegistry;
+
+  /**
+   * Constructs a \Drupal\Core\\Theme\Registry object.
+   *
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend interface to use for the complete theme registry data.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to use to load modules.
+   * @param string $theme_name
+   *   (optional) The name of the theme for which to construct the registry.
+   */
+  public function __construct(CacheBackendInterface $cache, LockBackendInterface $lock, ModuleHandlerInterface $module_handler, $theme_name = NULL) {
+    $this->cache = $cache;
+    $this->lock = $lock;
+    $this->moduleHandler = $module_handler;
+    $this->init($theme_name);
+  }
+
+  /**
+   * Initializes a theme with a certain name.
+   *
+   * This function does to much magic, so it should be replaced by another
+   * services which holds the current active theme information.
+   *
+   * @param string $theme_name
+   *   (optional) The name of the theme for which to construct the registry.+
+   */
+  protected function init($theme_name = NULL) {
+    // Unless instantiated for a specific theme, use globals.
+    if (!isset($theme_name)) {
+      // #1: The theme registry might get instantiated before the theme was
+      // initialized. Cope with that.
+      if (!isset($GLOBALS['theme_info']) || !isset($GLOBALS['theme'])) {
+        unset($this->runtimeRegistry);
+        unset($this->registry);
+        drupal_theme_initialize();
+      }
+      // #2: The testing framework only cares for the global $theme variable at
+      // this point. Cope with that.
+      if ($GLOBALS['theme'] != $GLOBALS['theme_info']->name) {
+        unset($this->runtimeRegistry);
+        unset($this->registry);
+        $this->initializeTheme();
+      }
+      $this->theme = $GLOBALS['theme_info'];
+      $this->baseThemes = $GLOBALS['base_theme_info'];
+      $this->engine = $GLOBALS['theme_engine'];
+    }
+    // Instead of the global theme, a specific theme was requested.
+    else {
+      // @see drupal_theme_initialize()
+      $themes = $this->listThemes();
+      $this->theme = $themes[$theme_name];
+
+      // Find all base themes.
+      $this->baseThemes = array();
+      $ancestor = $theme_name;
+      while ($ancestor && isset($themes[$ancestor]->base_theme)) {
+        $ancestor = $themes[$ancestor]->base_theme;
+        $this->baseThemes[] = $themes[$ancestor];
+        if (!empty($themes[$ancestor]->owner)) {
+          include_once DRUPAL_ROOT . '/' . $themes[$ancestor]->owner;
+        }
+      }
+      $this->baseThemes = array_reverse($this->baseThemes);
+
+      // @see _drupal_theme_initialize()
+      if (isset($this->theme->engine)) {
+        $this->engine = $this->theme->engine;
+        include_once DRUPAL_ROOT . '/' . $this->theme->owner;
+        if (function_exists($this->theme->engine . '_init')) {
+          foreach ($this->baseThemes as $base) {
+            call_user_func($this->theme->engine . '_init', $base);
+          }
+          call_user_func($this->theme->engine . '_init', $this->theme);
+        }
+      }
+    }
+  }
+
+  /**
+   * Returns the complete theme registry from cache or rebuilds it.
+   *
+   * @return array
+   *   The complete theme registry data array.
+   *
+   * @see Registry::$registry
+   */
+  public function get() {
+    if (isset($this->registry)) {
+      return $this->registry;
+    }
+    if ($cache = $this->cache->get('theme_registry:' . $this->theme->name)) {
+      $this->registry = $cache->data;
+    }
+    else {
+      $this->registry = $this->build();
+      // Only persist it if all modules are loaded to ensure it is complete.
+      if ($this->moduleHandler->isLoaded()) {
+        $this->setCache();
+      }
+    }
+    return $this->registry;
+  }
+
+  /**
+   * Returns the incomplete, runtime theme registry.
+   *
+   * @return \Drupal\Core\Utility\ThemeRegistry
+   *   A shared instance of the ThemeRegistry class, provides an ArrayObject
+   *   that allows it to be accessed with array syntax and isset(), and is more
+   *   lightweight than the full registry.
+   */
+  public function getRuntime() {
+    if (!isset($this->runtimeRegistry)) {
+      $this->runtimeRegistry = new ThemeRegistry('theme_registry:runtime:' . $this->theme->name, $this->cache, $this->lock, array('theme_registry' => TRUE), $this->moduleHandler->isLoaded());
+    }
+    return $this->runtimeRegistry;
+  }
+
+  /**
+   * Persists the theme registry in the cache backend.
+   */
+  protected function setCache() {
+    $this->cache->set('theme_registry:' . $this->theme->name, $this->registry, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
+  }
+
+  /**
+   * Returns the base hook for a given hook suggestion.
+   *
+   * @param string $hook
+   *   The name of a theme hook whose base hook to find.
+   *
+   * @return string|false
+   *   The name of the base hook or FALSE.
+   */
+  public function getBaseHook($hook) {
+    $base_hook = $hook;
+    // Iteratively strip everything after the last '__' delimiter, until a
+    // base hook definition is found. Recursive base hooks of base hooks are
+    // not supported, so the base hook must be an original implementation that
+    // points to a theme function or template.
+    while ($pos = strrpos($base_hook, '__')) {
+      $base_hook = substr($base_hook, 0, $pos);
+      if (isset($this->registry[$base_hook]['exists'])) {
+        break;
+      }
+    }
+    if ($pos !== FALSE && $base_hook !== $hook) {
+      return $base_hook;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Builds the theme registry cache.
+   *
+   * Theme hook definitions are collected in the following order:
+   * - Modules
+   * - Base theme engines
+   * - Base themes
+   * - Theme engine
+   * - Theme
+   *
+   * All theme hook definitions are essentially just collated and merged in the
+   * above order. However, various extension-specific default values and
+   * customizations are required; e.g., to record the effective file path for
+   * theme template. Therefore, this method first collects all extensions per
+   * type, and then dispatches the processing for each extension to
+   * processExtension().
+   *
+   * After completing the collection, modules are allowed to alter it. Lastly,
+   * any derived and incomplete theme hook definitions that are hook suggestions
+   * for base hooks (e.g., 'block__node' for the base hook 'block') need to be
+   * determined based on the full registry and classified as 'base hook'.
+   *
+   * @see theme()
+   * @see hook_theme_registry_alter()
+   *
+   * @return \Drupal\Core\Utility\ThemeRegistry
+   *   The build theme registry.
+   */
+  protected function build() {
+    $cache = array();
+    // First, preprocess the theme hooks advertised by modules. This will
+    // serve as the basic registry. Since the list of enabled modules is the
+    // same regardless of the theme used, this is cached in its own entry to
+    // save building it for every theme.
+    if ($cached = $this->cache->get('theme_registry:build:modules')) {
+      $cache = $cached->data;
+    }
+    else {
+      foreach ($this->moduleHandler->getImplementations('theme') as $module) {
+        $this->processExtension($cache, $module, 'module', $module, $this->getPath($module));
+      }
+      // Only cache this registry if all modules are loaded.
+      if ($this->moduleHandler->isLoaded()) {
+        $this->cache->set("theme_registry:build:modules", $cache, CacheBackendInterface::CACHE_PERMANENT, array('theme_registry' => TRUE));
+      }
+    }
+
+    // Process each base theme.
+    foreach ($this->baseThemes as $base) {
+      // If the base theme uses a theme engine, process its hooks.
+      $base_path = dirname($base->filename);
+      if ($this->engine) {
+        $this->processExtension($cache, $this->engine, 'base_theme_engine', $base->name, $base_path);
+      }
+      $this->processExtension($cache, $base->name, 'base_theme', $base->name, $base_path);
+    }
+
+    // And then the same thing, but for the theme.
+    if ($this->engine) {
+      $this->processExtension($cache, $this->engine, 'theme_engine', $this->theme->name, dirname($this->theme->filename));
+    }
+
+    // Finally, hooks provided by the theme itself.
+    $this->processExtension($cache, $this->theme->name, 'theme', $this->theme->name, dirname($this->theme->filename));
+
+    // Let modules alter the registry.
+    $this->moduleHandler->alter('theme_registry', $cache);
+
+    // @todo Implement more reduction of the theme registry entry.
+    // Optimize the registry to not have empty arrays for functions.
+    foreach ($cache as $hook => $info) {
+      if (empty($info['preprocess functions'])) {
+        unset($cache[$hook]['preprocess functions']);
+      }
+    }
+    $this->registry = $cache;
+
+    return $this->registry;
+  }
+
+  /**
+   * Process a single implementation of hook_theme().
+   *
+   * @param $cache
+   *   The theme registry that will eventually be cached; It is an associative
+   *   array keyed by theme hooks, whose values are associative arrays
+   *   describing the hook:
+   *   - 'type': The passed-in $type.
+   *   - 'theme path': The passed-in $path.
+   *   - 'function': The name of the function generating output for this theme
+   *     hook. Either defined explicitly in hook_theme() or, if neither
+   *     'function' nor 'template' is defined, then the default theme function
+   *     name is used. The default theme function name is the theme hook
+   *     prefixed by either 'theme_' for modules or '$name_' for everything
+   *     else. If 'function' is defined, 'template' is not used.
+   *   - 'template': The filename of the template generating output for this
+   *     theme hook. The template is in the directory defined by the 'path' key
+   *     of hook_theme() or defaults to "$path/templates".
+   *   - 'variables': The variables for this theme hook as defined in
+   *     hook_theme(). If there is more than one implementation and 'variables'
+   *     is not specified in a later one, then the previous definition is kept.
+   *   - 'render element': The renderable element for this theme hook as defined
+   *     in hook_theme(). If there is more than one implementation and
+   *     'render element' is not specified in a later one, then the previous
+   *     definition is kept.
+   *   - 'preprocess functions': See theme() for detailed documentation.
+   * @param string $name
+   *   The name of the module, theme engine, base theme engine, theme or base
+   *   theme implementing hook_theme().
+   * @param string $type
+   *   One of 'module', 'theme_engine', 'base_theme_engine', 'theme', or
+   *   'base_theme'. Unlike regular hooks that can only be implemented by
+   *   modules, each of these can implement hook_theme(). This function is
+   *   called in aforementioned order and new entries override older ones. For
+   *   example, if a theme hook is both defined by a module and a theme, then
+   *   the definition in the theme will be used.
+   * @param \stdClass $theme
+   *   The loaded $theme object as returned from list_themes().
+   * @param string $path
+   *   The directory where $name is. For example, modules/system or
+   *   themes/bartik.
+   *
+   * @see theme()
+   * @see hook_theme()
+   * @see list_themes()
+   */
+  protected function processExtension(&$cache, $name, $type, $theme, $path) {
+    $result = array();
+
+    $hook_defaults = array(
+      'variables' => TRUE,
+      'render element' => TRUE,
+      'pattern' => TRUE,
+      'base hook' => TRUE,
+    );
+
+    $module_list = array_keys((array) $this->moduleHandler->getModuleList());
+
+    // Invoke the hook_theme() implementation, preprocess what is returned, and
+    // merge it into $cache.
+    $function = $name . '_theme';
+    if (function_exists($function)) {
+      $result = $function($cache, $type, $theme, $path);
+      foreach ($result as $hook => $info) {
+        // When a theme or engine overrides a module's theme function
+        // $result[$hook] will only contain key/value pairs for information being
+        // overridden.  Pull the rest of the information from what was defined by
+        // an earlier hook.
+
+        // Fill in the type and path of the module, theme, or engine that
+        // implements this theme function.
+        $result[$hook]['type'] = $type;
+        $result[$hook]['theme path'] = $path;
+
+        // If function and file are omitted, default to standard naming
+        // conventions.
+        if (!isset($info['template']) && !isset($info['function'])) {
+          $result[$hook]['function'] = ($type == 'module' ? 'theme_' : $name . '_') . $hook;
+        }
+
+        if (isset($cache[$hook]['includes'])) {
+          $result[$hook]['includes'] = $cache[$hook]['includes'];
+        }
+
+        // If the theme implementation defines a file, then also use the path
+        // that it defined. Otherwise use the default path. This allows
+        // system.module to declare theme functions on behalf of core .include
+        // files.
+        if (isset($info['file'])) {
+          $include_file = isset($info['path']) ? $info['path'] : $path;
+          $include_file .= '/' . $info['file'];
+          include_once DRUPAL_ROOT . '/' . $include_file;
+          $result[$hook]['includes'][] = $include_file;
+        }
+
+        // If the default keys are not set, use the default values registered
+        // by the module.
+        if (isset($cache[$hook])) {
+          $result[$hook] += array_intersect_key($cache[$hook], $hook_defaults);
+        }
+
+        // The following apply only to theming hooks implemented as templates.
+        if (isset($info['template'])) {
+          // Prepend the current theming path when none is set.
+          if (!isset($info['path'])) {
+            $result[$hook]['template'] = $path . '/templates/' . $info['template'];
+          }
+        }
+
+        // Preprocess variables for all theming hooks, whether the hook is
+        // implemented as a template or as a function. Ensure they are arrays.
+        if (!isset($info['preprocess functions']) || !is_array($info['preprocess functions'])) {
+          $info['preprocess functions'] = array();
+          $prefixes = array();
+          if ($type == 'module') {
+            // Default variable preprocessor prefix.
+            $prefixes[] = 'template';
+            // Add all modules so they can intervene with their own variable
+            // preprocessors. This allows them to provide variable preprocessors
+            // even if they are not the owner of the current hook.
+            $prefixes = array_merge($prefixes, $module_list);
+          }
+          elseif ($type == 'theme_engine' || $type == 'base_theme_engine') {
+            // Theme engines get an extra set that come before the normally
+            // named variable preprocessors.
+            $prefixes[] = $name . '_engine';
+            // The theme engine registers on behalf of the theme using the
+            // theme's name.
+            $prefixes[] = $theme;
+          }
+          else {
+            // This applies when the theme manually registers their own variable
+            // preprocessors.
+            $prefixes[] = $name;
+          }
+          foreach ($prefixes as $prefix) {
+            // Only use non-hook-specific variable preprocessors for theming
+            // hooks implemented as templates. See theme().
+            if (isset($info['template']) && function_exists($prefix . '_preprocess')) {
+              $info['preprocess functions'][] = $prefix . '_preprocess';
+            }
+            if (function_exists($prefix . '_preprocess_' . $hook)) {
+              $info['preprocess functions'][] = $prefix . '_preprocess_' . $hook;
+            }
+          }
+        }
+        // Check for the override flag and prevent the cached variable
+        // preprocessors from being used. This allows themes or theme engines
+        // to remove variable preprocessors set earlier in the registry build.
+        if (!empty($info['override preprocess functions'])) {
+          // Flag not needed inside the registry.
+          unset($result[$hook]['override preprocess functions']);
+        }
+        elseif (isset($cache[$hook]['preprocess functions']) && is_array($cache[$hook]['preprocess functions'])) {
+          $info['preprocess functions'] = array_merge($cache[$hook]['preprocess functions'], $info['preprocess functions']);
+        }
+        $result[$hook]['preprocess functions'] = $info['preprocess functions'];
+      }
+
+      // Merge the newly created theme hooks into the existing cache.
+      $cache = $result + $cache;
+    }
+
+    // Let themes have variable preprocessors even if they didn't register a
+    // template.
+    if ($type == 'theme' || $type == 'base_theme') {
+      foreach ($cache as $hook => $info) {
+        // Check only if not registered by the theme or engine.
+        if (empty($result[$hook])) {
+          if (!isset($info['preprocess functions'])) {
+            $cache[$hook]['preprocess functions'] = array();
+          }
+          // Only use non-hook-specific variable preprocessors for theme hooks
+          // implemented as templates. See theme().
+          if (isset($info['template']) && function_exists($name . '_preprocess')) {
+            $cache[$hook]['preprocess functions'][] = $name . '_preprocess';
+          }
+          if (function_exists($name . '_preprocess_' . $hook)) {
+            $cache[$hook]['preprocess functions'][] = $name . '_preprocess_' . $hook;
+            $cache[$hook]['theme path'] = $path;
+          }
+          // Ensure uniqueness.
+          $cache[$hook]['preprocess functions'] = array_unique($cache[$hook]['preprocess functions']);
+        }
+      }
+    }
+  }
+
+  /**
+   * Invalidates theme registry caches.
+   *
+   * To be called when the list of enabled extensions is changed.
+   */
+  public function reset() {
+
+    // Reset the runtime registry.
+    if (isset($this->runtimeRegistry) && $this->runtimeRegistry instanceof ThemeRegistry) {
+      $this->runtimeRegistry->clear();
+    }
+    $this->runtimeRegistry = NULL;
+
+    $this->registry = NULL;
+    $this->cache->invalidateTags(array('theme_registry' => TRUE));
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function destruct() {
+    if (isset($this->runtimeRegistry)) {
+      $this->runtimeRegistry->destruct();
+    }
+  }
+
+  /**
+   * Wraps drupal_get_path().
+   *
+   * @param string $module
+   *   The name of the item for which the path is requested.
+   *
+   * @return string
+   */
+  protected function getPath($module) {
+    return drupal_get_path('module', $module);
+  }
+
+  /**
+   * Wraps list_themes().
+   *
+   * @return array
+   */
+  protected function listThemes() {
+    return list_themes();
+  }
+
+  /**
+   * Wraps drupal_theme_initialize().
+   */
+  protected function initializeTheme() {
+    drupal_theme_initialize();
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Utility/ThemeRegistry.php b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
index 9263e51904afe1061a79a5245f15f7fbf6dec39a..0979e07f08109308b34a61c75e54833f22f9ec66 100644
--- a/core/lib/Drupal/Core/Utility/ThemeRegistry.php
+++ b/core/lib/Drupal/Core/Utility/ThemeRegistry.php
@@ -8,16 +8,19 @@
 namespace Drupal\Core\Utility;
 
 use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Cache\CacheCollector;
+use Drupal\Core\DestructableInterface;
+use Drupal\Core\Lock\LockBackendInterface;
 
 /**
  * Builds the run-time theme registry.
  *
- * Extends CacheArray to allow the theme registry to be accessed as a
+ * A cache collector to allow the theme registry to be accessed as a
  * complete registry, while internally caching only the parts of the registry
  * that are actually in use on the site. On cache misses the complete
  * theme registry is loaded and used to update the run-time cache.
  */
-class ThemeRegistry extends CacheArray {
+class ThemeRegistry extends CacheCollector implements DestructableInterface {
 
   /**
    * Whether the partial registry can be persisted to the cache.
@@ -38,35 +41,42 @@ class ThemeRegistry extends CacheArray {
    *
    * @param string $cid
    *   The cid for the array being cached.
-   * @param string $bin
-   *   The bin to cache the array.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   The cache backend.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   The lock backend.
    * @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, $bin, $tags, $modules_loaded = FALSE) {
-
+  function __construct($cid, CacheBackendInterface $cache, LockBackendInterface $lock, $tags = array(), $modules_loaded = FALSE) {
     $this->cid = $cid;
-    $this->bin = $bin;
+    $this->cache = $cache;
+    $this->lock = $lock;
     $this->tags = $tags;
     $request = \Drupal::request();
     $this->persistable = $modules_loaded && $request->isMethod('GET');
 
-    if ($this->persistable && $cached = cache($this->bin)->get($this->cid)) {
-      $data = $cached->data;
+     // @todo: Implement lazyload.
+    $this->cacheLoaded = TRUE;
+
+    if ($this->persistable && $cached = $this->cache->get($this->cid)) {
+      $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().
-      $data = $this->initializeRegistry();
-      if ($this->persistable) {
-        $this->set($data);
+      $this->storage = $this->initializeRegistry();
+      foreach (array_keys($this->storage) as $key) {
+        $this->persist($key);
       }
+      // RegistryTest::testRaceCondition() ensures that the cache entry is
+      // written on the initial construction of the theme registry.
+      $this->updateCache();
     }
-    $this->storage = $data;
   }
 
   /**
@@ -77,58 +87,73 @@ function __construct($cid, $bin, $tags, $modules_loaded = FALSE) {
    *   initialized to NULL.
    */
   function initializeRegistry() {
-    $this->completeRegistry = theme_get_registry();
+    // @todo DIC this.
+    $this->completeRegistry = \Drupal::service('theme.registry')->get();
 
     return array_fill_keys(array_keys($this->completeRegistry), NULL);
   }
 
   /**
-   * Overrides CacheArray::offsetExists().
+   * {@inheritdoc}
    */
-  public function offsetExists($offset) {
+  public function has($key) {
     // Since the theme registry allows for theme hooks to be requested that
     // 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($offset, $this->storage);
+    return array_key_exists($key, $this->storage);
   }
 
   /**
-   * Overrides CacheArray::offsetGet().
+   * {@inheritdoc}
    */
-  public function offsetGet($offset) {
+  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[$offset])) {
-      return $this->storage[$offset];
+    if (isset($this->storage[$key])) {
+      return $this->storage[$key];
     }
-    elseif (array_key_exists($offset, $this->storage)) {
-      return $this->resolveCacheMiss($offset);
+    elseif (array_key_exists($key, $this->storage)) {
+      return $this->resolveCacheMiss($key);
     }
   }
 
   /**
-   * Implements CacheArray::resolveCacheMiss().
+   * {@inheritdoc}
    */
-  public function resolveCacheMiss($offset) {
+  public function resolveCacheMiss($key) {
     if (!isset($this->completeRegistry)) {
-      $this->completeRegistry = theme_get_registry();
+      $this->completeRegistry = \Drupal::service('theme.registry')->get();
     }
-    $this->storage[$offset] = $this->completeRegistry[$offset];
+    $this->storage[$key] = $this->completeRegistry[$key];
     if ($this->persistable) {
-      $this->persist($offset);
+      $this->persist($key);
     }
-    return $this->storage[$offset];
+    return $this->storage[$key];
   }
 
   /**
-   * Overrides CacheArray::set().
+   * {@inheritdoc}
    */
-  public function set($data, $lock = TRUE) {
-    $lock_name = $this->cid . ':' . $this->bin;
-    if (!$lock || lock()->acquire($lock_name)) {
-      if ($cached = cache($this->bin)->get($this->cid)) {
+  protected function updateCache($lock = TRUE) {
+    if (!$this->persistable) {
+      return;
+    }
+    // @todo: Is the custom implementation necessary?
+    $data = array();
+    foreach ($this->keysToPersist as $offset => $persist) {
+      if ($persist) {
+        $data[$offset] = $this->storage[$offset];
+      }
+    }
+    if (empty($data)) {
+      return;
+    }
+
+    $lock_name = $this->cid . ':' . __CLASS__;
+    if (!$lock || $this->lock->acquire($lock_name)) {
+      if ($cached = $this->cache->get($this->cid)) {
         // 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);
@@ -137,10 +162,11 @@ public function set($data, $lock = TRUE) {
         $registry = $this->initializeRegistry();
         $data = array_merge($registry, $data);
       }
-      cache($this->bin)->set($this->cid, $data, CacheBackendInterface::CACHE_PERMANENT, $this->tags);
+      $this->cache->set($this->cid, $data, CacheBackendInterface::CACHE_PERMANENT, $this->tags);
       if ($lock) {
-        lock()->release($lock_name);
+        $this->lock->release($lock_name);
       }
     }
   }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
index 5ab43ca6c5f63c79aa2b660f4f18e72f88bb24df..3e4d6afa7b5ca6d9448e5b5bc1cca9a1fa6149ad 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/RegistryTest.php
@@ -35,17 +35,19 @@ public static function getInfo() {
    * Tests the behavior of the theme registry class.
    */
   function testRaceCondition() {
-    $_SERVER['REQUEST_METHOD'] = 'GET';
+    \Drupal::request()->setMethod('GET');
     $cid = 'test_theme_registry';
 
     // Directly instantiate the theme registry, this will cause a base cache
     // entry to be written in __construct().
-    $registry = new ThemeRegistry($cid, 'cache', array('theme_registry' => TRUE), $this->container->get('module_handler')->isLoaded());
+    $cache = \Drupal::cache('cache');
+    $lock_backend = \Drupal::lock();
+    $registry = new ThemeRegistry($cid, $cache, $lock_backend, array('theme_registry' => TRUE), $this->container->get('module_handler')->isLoaded());
 
     $this->assertTrue(cache()->get($cid), 'Cache entry was created.');
 
     // Trigger a cache miss for an offset.
-    $this->assertTrue($registry['theme_test_template_test'], 'Offset was returned correctly from the theme registry.');
+    $this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry.');
     // 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
@@ -53,15 +55,15 @@ function testRaceCondition() {
     cache()->delete($cid);
 
     // Destroy the class so that it triggers a cache write for the offset.
-    unset($registry);
+    $registry->destruct();
 
     $this->assertTrue(cache()->get($cid), 'Cache entry was created.');
 
     // Create a new instance of the class. Confirm that both the offset
     // requested previously, and one that has not yet been requested are both
     // available.
-    $registry = new ThemeRegistry($cid, 'cache', array('theme_registry' => TRUE));
-    $this->assertTrue($registry['theme_test_template_test'], 'Offset was returned correctly from the theme registry');
-    $this->assertTrue($registry['theme_test_template_test_2'], 'Offset was returned correctly from the theme registry');
+    $registry = new ThemeRegistry($cid, $cache, $lock_backend, array('theme_registry' => TRUE), $this->container->get('module_handler')->isLoaded());
+    $this->assertTrue($registry->get('theme_test_template_test'), 'Offset was returned correctly from the theme registry');
+    $this->assertTrue($registry->get('theme_test_template_test_2'), 'Offset was returned correctly from the theme registry');
   }
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
index 9c217952e395aa87bebe7df24750be108d7c8e5a..069960c943d9ea152cfc857241156cc6dbff82c4 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/ThemeTest.php
@@ -250,15 +250,9 @@ function testClassLoading() {
    * Tests drupal_find_theme_templates().
    */
   public function testFindThemeTemplates() {
-    $cache = array();
-
-    // Prime the theme cache.
-    foreach (\Drupal::moduleHandler()->getImplementations('theme') as $module) {
-      _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
-    }
-
-    $templates = drupal_find_theme_templates($cache, '.html.twig', drupal_get_path('theme', 'test_theme'));
-    $this->assertEqual($templates['node__1']['template'], 'node--1', 'Template node--1.html.twig was found in test_theme.');
+    $registry = $this->container->get('theme.registry')->get();
+    $templates = drupal_find_theme_templates($registry, '.html.twig', drupal_get_path('theme', 'test_theme'));
+    $this->assertEqual($templates['node__1']['template'], 'node--1', 'Template node--1.tpl.twig was found in test_theme.');
   }
 
   /**
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
index 53d6a0b77a8747f4ceb6d72a7a488fa51fbe0785..3a26efeff04e975ebf021945c4dfba8d88e0d448 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigDebugMarkupTest.php
@@ -41,11 +41,7 @@ function testTwigDebugMarkup() {
     $this->rebuildContainer();
     $this->resetAll();
 
-    $cache = array();
-    // Prime the theme cache.
-    foreach (\Drupal::moduleHandler()->getImplementations('theme') as $module) {
-      _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
-    }
+    $cache = $this->container->get('theme.registry')->get();
     // Create array of Twig templates.
     $templates = drupal_find_theme_templates($cache, $extension, drupal_get_path('theme', 'test_theme'));
     $templates += drupal_find_theme_templates($cache, $extension, drupal_get_path('module', 'node'));
diff --git a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
index 958128c7c3cc8abf6148777afaa6a1aa8aed47ee..5689b3d07b5491ce707d7a90567db8ade608b2d5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Theme/TwigSettingsTest.php
@@ -82,18 +82,24 @@ function testTwigCacheOverride() {
       ->set('default', 'test_theme')
       ->save();
 
-    $cache = array();
-    // Prime the theme cache.
-    foreach (\Drupal::moduleHandler()->getImplementations('theme') as $module) {
-      _theme_process_registry($cache, $module, 'module', $module, drupal_get_path('module', $module));
-    }
+    // Unset the global variables, so \Drupal\Core\Theme\Registry::init() fires
+    // drupal_theme_initialize, which fills up the global variables  properly
+    // and chosen the current active theme.
+    unset($GLOBALS['theme_info']);
+    unset($GLOBALS['theme']);
+    // Reset the theme registry, so that the new theme is used.
+    $this->container->set('theme.registry', NULL);
 
     // Load array of Twig templates.
-    $templates = drupal_find_theme_templates($cache, $extension, drupal_get_path('theme', 'test_theme'));
+    $registry = $this->container->get('theme.registry');
+    $registry->reset();
+
+    $templates = $registry->getRuntime();
 
     // Get the template filename and the cache filename for
     // theme_test.template_test.html.twig.
-    $template_filename = $templates['theme_test_template_test']['path'] . '/' . $templates['theme_test_template_test']['template'] . $extension;
+    $info = $templates->get('theme_test_template_test');
+    $template_filename = $info['path'] . '/' . $info['template'] . $extension;
     $cache_filename = $this->container->get('twig')->getCacheFilename($template_filename);
 
     // Navigate to the page and make sure the template gets cached.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index ba23bb9b3ae23761f4d4c79517009aedc0474735..de2c7e2a749893c40a23cf1726c154791c742bac 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -1362,7 +1362,7 @@ function hook_theme($existing, $type, $theme, $path) {
  *
  * The $theme_registry array is keyed by theme hook name, and contains the
  * information returned from hook_theme(), as well as additional properties
- * added by _theme_process_registry().
+ * added by \Drupal\Core\Theme\Registry::processExtension().
  *
  * For example:
  * @code
@@ -1385,7 +1385,7 @@ function hook_theme($existing, $type, $theme, $path) {
  *   The entire cache of theme registry information, post-processing.
  *
  * @see hook_theme()
- * @see _theme_process_registry()
+ * @see \Drupal\Core\Theme\Registry::processExtension()
  */
 function hook_theme_registry_alter(&$theme_registry) {
   // Kill the next/previous forum topic navigation links.
diff --git a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/EventSubscriber/ThemeTestSubscriber.php b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/EventSubscriber/ThemeTestSubscriber.php
index 56cbdafda44517027df1d329920636a3e1b7c21b..2e5417d4804eb1afc3bc523371f88c819acc27d8 100644
--- a/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/EventSubscriber/ThemeTestSubscriber.php
+++ b/core/modules/system/tests/modules/theme_test/lib/Drupal/theme_test/EventSubscriber/ThemeTestSubscriber.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\theme_test\EventSubscriber;
 
+use Symfony\Component\DependencyInjection\ContainerAware;
 use Symfony\Component\HttpKernel\KernelEvents;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
 use Symfony\Component\EventDispatcher\EventSubscriberInterface;
@@ -14,7 +15,14 @@
 /**
  * Theme test subscriber for controller requests.
  */
-class ThemeTestSubscriber implements EventSubscriberInterface {
+class ThemeTestSubscriber extends ContainerAware implements EventSubscriberInterface {
+
+  /**
+   * The used container.
+   *
+   * @var \Symfony\Component\DependencyInjection\IntrospectableContainerInterface
+   */
+  protected $container;
 
   /**
    * Generates themed output early in a page request.
@@ -36,10 +44,18 @@ public function onRequest(GetResponseEvent $event) {
       // returning output and theming the page as a whole.
       $GLOBALS['theme_test_output'] = theme('more_link', array('url' => 'user', 'title' => 'Themed output generated in a KernelEvents::REQUEST listener'));
     }
+  }
+
+  /**
+   * Ensures that the theme registry was not initialized.
+   */
+  public function onView(GetResponseEvent $event) {
+    $request = $event->getRequest();
+    $current_path = $request->attributes->get('_system_path');
     if (strpos($current_path, 'user/autocomplete') === 0) {
-      // Register a fake registry loading callback. If it gets called by
-      // theme_get_registry(), the registry has not been initialized yet.
-      _theme_registry_callback('_theme_test_load_registry', array());
+      if ($this->container->initialized('theme.registry')) {
+        throw new \Exception('registry initialized');
+      }
     }
   }
 
@@ -48,6 +64,7 @@ public function onRequest(GetResponseEvent $event) {
    */
   static function getSubscribedEvents() {
     $events[KernelEvents::REQUEST][] = array('onRequest');
+    $events[KernelEvents::VIEW][] = array('onView', -1000);
     return $events;
   }
 
diff --git a/core/modules/system/tests/modules/theme_test/theme_test.module b/core/modules/system/tests/modules/theme_test/theme_test.module
index badea8129354b980f6c8988c15e9d18219a5e479..87e26b8922836ee8eb3d44e01e835903db091629 100644
--- a/core/modules/system/tests/modules/theme_test/theme_test.module
+++ b/core/modules/system/tests/modules/theme_test/theme_test.module
@@ -42,6 +42,9 @@ function theme_test_theme($existing, $type, $theme, $path) {
   $items['theme_test_function_template_override'] = array(
     'variables' => array(),
   );
+  $info['test_theme_not_existing_function'] = array(
+    'function' => 'test_theme_not_existing_function',
+  );
   return $items;
 }
 
@@ -76,13 +79,6 @@ function theme_test_menu() {
   );
   return $items;
 }
-/**
- * Fake registry loading callback.
- */
-function _theme_test_load_registry() {
-  print 'registry initialized';
-  return array();
-}
 
 /**
  * Custom theme callback.
diff --git a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
index dd548bf26f31cf790b61a9425da1056ea01bcbe0..7af12f77c7acfc15d129bfb389ac5c1382c4e3f5 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/views/display/DisplayPluginBase.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Utility\String;
 use Drupal\Core\Language\Language;
+use Drupal\Core\Theme\Registry;
 use Drupal\views\Plugin\views\area\AreaPluginBase;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\PluginBase;
@@ -1756,54 +1757,21 @@ public function buildOptionsForm(&$form, &$form_state) {
         }
 
         if (isset($GLOBALS['theme']) && $GLOBALS['theme'] == $this->theme) {
-          $this->theme_registry = theme_get_registry();
+          $this->theme_registry = \Drupal::service('theme.registry')->get();
           $theme_engine = $GLOBALS['theme_engine'];
         }
         else {
           $themes = list_themes();
           $theme = $themes[$this->theme];
 
-          // Find all our ancestor themes and put them in an array.
-          $base_theme = array();
-          $ancestor = $this->theme;
-          while ($ancestor && isset($themes[$ancestor]->base_theme)) {
-            $ancestor = $themes[$ancestor]->base_theme;
-            $base_theme[] = $themes[$ancestor];
-          }
-
-          // The base themes should be initialized in the right order.
-          $base_theme = array_reverse($base_theme);
-
-          // This code is copied directly from _drupal_theme_initialize()
+          // @see _drupal_theme_initialize()
           $theme_engine = NULL;
 
-          // Initialize the theme.
           if (isset($theme->engine)) {
-            // Include the engine.
-            include_once DRUPAL_ROOT . '/' . $theme->owner;
-
             $theme_engine = $theme->engine;
-            if (function_exists($theme_engine . '_init')) {
-              foreach ($base_theme as $base) {
-                call_user_func($theme_engine . '_init', $base);
-              }
-              call_user_func($theme_engine . '_init', $theme);
-            }
           }
-          else {
-            // include non-engine theme files
-            foreach ($base_theme as $base) {
-              // Include the theme file or the engine.
-              if (!empty($base->owner)) {
-                include_once DRUPAL_ROOT . '/' . $base->owner;
-              }
-            }
-            // and our theme gets one too.
-            if (!empty($theme->owner)) {
-              include_once DRUPAL_ROOT . '/' . $theme->owner;
-            }
-          }
-          $this->theme_registry = _theme_load_registry($theme, $base_theme, $theme_engine);
+          $cache_theme = \Drupal::service('cache.theme');
+          $this->theme_registry = new Registry($cache_theme, \Drupal::lock(), \Drupal::moduleHandler(), $theme->name);
         }
 
         // If there's a theme engine involved, we also need to know its extension
@@ -2071,15 +2039,9 @@ public function buildOptionsForm(&$form, &$form_state) {
    * a templates rescan).
    */
   public function rescanThemes($form, &$form_state) {
-    drupal_theme_rebuild();
-
-    // The 'Theme: Information' page is about to be shown again. That page
-    // analyzes the output of theme_get_registry(). However, this latter
-    // function uses an internal cache (which was initialized before we
-    // called drupal_theme_rebuild()) so it won't reflect the
-    // current state of our theme registry. The only way to clear that cache
-    // is to re-initialize the theme system:
-    unset($GLOBALS['theme']);
+    // Analyzes the data of the theme registry.
+    \Drupal::service('theme.registry')->reset();
+
     drupal_theme_initialize();
 
     $form_state['rerender'] = TRUE;
diff --git a/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..399f22312626b0a22654c13794383d76b533bb4f
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Theme/RegistryTest.php
@@ -0,0 +1,150 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Theme\RegistryTest.
+ */
+
+namespace Drupal\Tests\Core\Theme;
+
+use Drupal\Core\Theme\Registry;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the theme registry service.
+ *
+ * @group Drupal
+ * @group ${group}
+ *
+ * @see \Drupal\Core\Theme\Registry
+ */
+class RegistryTest extends UnitTestCase {
+
+  /**
+   * The tested theme registry.
+   *
+   * @var \Drupal\Tests\Core\Theme\TestRegistry
+   */
+  protected $registry;
+
+  /**
+   * The mocked cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
+   * The mocked lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $lock;
+
+  /**
+   * The mocked module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Theme Registry',
+      'description' => 'Tests the theme registry.',
+      'group' => 'Theme',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->cache = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
+    $this->lock = $this->getMock('Drupal\Core\Lock\LockBackendInterface');
+    $this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
+
+    $this->setupTheme();
+  }
+
+  /**
+   * Tests getting the theme registry defined by a module.
+   */
+  public function testGetRegistryForModule() {
+    $this->setupTheme('test_theme');
+    $this->registry->setTheme((object) array(
+      'name' => 'test_theme',
+      'filename' => 'core/modules/system/tests/themes/test_theme/test_theme.theme',
+    ));
+    $this->registry->setBaseThemes(array());
+
+    // Include the module so that hook_theme can be called.
+    include_once DRUPAL_ROOT . '/core/modules/system/tests/modules/theme_test/theme_test.module';
+    $this->moduleHandler->expects($this->once())
+      ->method('getImplementations')
+      ->with('theme')
+      ->will($this->returnValue(array('theme_test')));
+
+    $registry = $this->registry->get();
+
+    // Ensure that the registry entries from the module are found.
+    $this->assertArrayHasKey('theme_test', $registry);
+    $this->assertArrayHasKey('theme_test_template_test', $registry);
+    $this->assertArrayHasKey('theme_test_template_test_2', $registry);
+    $this->assertArrayHasKey('theme_test_suggestion_provided', $registry);
+    $this->assertArrayHasKey('theme_test_specific_suggestions', $registry);
+    $this->assertArrayHasKey('theme_test_suggestions', $registry);
+    $this->assertArrayHasKey('theme_test_function_suggestions', $registry);
+    $this->assertArrayHasKey('theme_test_foo', $registry);
+    $this->assertArrayHasKey('theme_test_render_element', $registry);
+    $this->assertArrayHasKey('theme_test_render_element_children', $registry);
+    $this->assertArrayHasKey('theme_test_function_template_override', $registry);
+
+    $this->assertArrayNotHasKey('test_theme_not_existing_function', $registry);
+
+    $info = $registry['theme_test_function_suggestions'];
+    $this->assertEquals('module', $info['type']);
+    $this->assertEquals('core/modules/system/tests/modules/theme_test', $info['theme path']);
+    $this->assertEquals('theme_theme_test_function_suggestions', $info['function']);
+    $this->assertEquals(array(), $info['variables']);
+  }
+
+  protected function setupTheme($theme_name = NULL) {
+    $this->registry = new TestRegistry($this->cache, $this->lock, $this->moduleHandler, $theme_name);
+  }
+
+}
+
+class TestRegistry extends Registry {
+
+  public function setTheme(\stdClass $theme) {
+    $this->theme = $theme;
+  }
+
+  public function setBaseThemes(array $base_themes) {
+    $this->baseThemes = $base_themes;
+  }
+
+  protected function init($theme_name = NULL) {
+  }
+
+  protected function getPath($module) {
+    if ($module == 'theme_test') {
+      return 'core/modules/system/tests/modules/theme_test';
+    }
+  }
+
+  protected function listThemes() {
+  }
+
+  protected function initializeTheme() {
+  }
+
+}
+
+if (!defined('DRUPAL_ROOT')) {
+  define('DRUPAL_ROOT', dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__)))));
+}