diff --git includes/bootstrap.inc includes/bootstrap.inc
index 8fe5bea..11b5f8a 100644
--- includes/bootstrap.inc
+++ includes/bootstrap.inc
@@ -857,11 +857,28 @@ function drupal_page_get_cache($check_only = FALSE) {
   }
 
   if (drupal_page_is_cacheable()) {
-    $cache = cache_get($base_root . request_uri(), 'cache_page');
+    // If the site is configured to load the page from cache without hitting
+    // the database, drupal_page_get_cache() could get called before the lock
+    // system has been initialized.
+    if (variable_get('page_cache_without_database')) {
+      require_once DRUPAL_ROOT . '/' . variable_get('lock_inc', 'includes/lock.inc');
+      lock_initialize();
+    }
+
+    $cid = $base_root . request_uri();
+    if (!($cache = cache_get($cid, 'cache_page')) && !lock_acquire($cid)) {
+      lock_wait($cid);
+      if (!$cache = cache_get($cid, 'cache_page')) {
+        // Proceed with page build, cache its result, then release the lock.
+        lock_acquire($cid);
+      }
+    }
+
     if ($cache !== FALSE) {
       $cache_hit = TRUE;
+      return $cache;
     }
-    return $cache;
+    header('X-Drupal-Cache: MISS');
   }
 }
 
@@ -2150,13 +2167,17 @@ function _drupal_bootstrap_page_cache() {
   foreach (variable_get('cache_backends', array()) as $include) {
     require_once DRUPAL_ROOT . '/' . $include;
   }
-  // Check for a cache mode force from settings.php.
+  // Check for a cache mode force from settings.php. It's not safe to invoke
+  // hooks without the database, so set a flag based on the value of
+  // $conf['page_cache_without_database'].
   if (variable_get('page_cache_without_database')) {
     $cache_enabled = TRUE;
+    $hook_invocation_is_safe = FALSE;
   }
   else {
     drupal_bootstrap(DRUPAL_BOOTSTRAP_VARIABLES, FALSE);
     $cache_enabled = variable_get('cache');
+    $hook_invocation_is_safe = TRUE;
   }
   drupal_block_denied(ip_address());
   // If there is no session cookie and cache is enabled (or forced), try
@@ -2174,23 +2195,18 @@ function _drupal_bootstrap_page_cache() {
       $_GET['q'] = $cache->data['path'];
       drupal_set_title($cache->data['title'], PASS_THROUGH);
       date_default_timezone_set(drupal_get_user_timezone());
-      // If the skipping of the bootstrap hooks is not enforced, call
-      // hook_boot.
-      if (variable_get('page_cache_invoke_hooks', TRUE)) {
+      // If the skipping of bootstrap hooks is not enforced, call hook_boot().
+      if ($hook_invocation_is_safe && variable_get('page_cache_invoke_hooks', TRUE)) {
         bootstrap_invoke_all('boot');
       }
       drupal_serve_page_from_cache($cache);
-      // If the skipping of the bootstrap hooks is not enforced, call
-      // hook_exit.
-      if (variable_get('page_cache_invoke_hooks', TRUE)) {
+      // If the skipping of bootstrap hooks is not enforced, call hook_exit().
+      if ($hook_invocation_is_safe && variable_get('page_cache_invoke_hooks', TRUE)) {
         bootstrap_invoke_all('exit');
       }
       // We are done.
       exit;
     }
-    else {
-      header('X-Drupal-Cache: MISS');
-    }
   }
 }
 
diff --git includes/common.inc includes/common.inc
index 1903646..064cf84 100644
--- includes/common.inc
+++ includes/common.inc
@@ -4978,6 +4978,7 @@ function drupal_page_set_cache() {
         $cache->data['body'] = gzencode($cache->data['body'], 9, FORCE_GZIP);
       }
       cache_set($cache->cid, $cache->data, 'cache_page', $cache->expire);
+      lock_release($cache->cid);
     }
     return $cache;
   }
@@ -5756,7 +5757,14 @@ function drupal_render_cache_get($elements) {
   }
   $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
 
-  if (!empty($cid) && $cache = cache_get($cid, $bin)) {
+  if (!($cache = cache_get($cid, $bin)) && !lock_acquire($cid)) {
+    lock_wait($cid);
+    if (!$cache = cache_get($cid, $bin)) {
+      lock_acquire($cid);
+    }
+  }
+
+  if ($cache) {
     // Add additional libraries, JavaScript, CSS and other data attached
     // to this element.
     if (isset($cache->data['#attached'])) {
@@ -5765,6 +5773,10 @@ function drupal_render_cache_get($elements) {
     // Return the rendered output.
     return $cache->data['#markup'];
   }
+
+  // Proceed with rendering. Even if we failed to acquire the lock, we
+  // should try to render the elements. Locking is a pluggable system, so the
+  // site may still function even when repeated attempts to use it fail.
   return FALSE;
 }
 
@@ -5801,6 +5813,7 @@ function drupal_render_cache_set(&$markup, $elements) {
   $bin = isset($elements['#cache']['bin']) ? $elements['#cache']['bin'] : 'cache';
   $expire = isset($elements['#cache']['expire']) ? $elements['#cache']['expire'] : CACHE_PERMANENT;
   cache_set($cid, $data, $bin, $expire);
+  lock_release($cid);
 }
 
 /**
diff --git includes/lock.inc includes/lock.inc
index f23c499..0d99b6f 100644
--- includes/lock.inc
+++ includes/lock.inc
@@ -197,12 +197,18 @@ function lock_may_be_available($name) {
  *   TRUE if the lock holds, FALSE if it is available.
  */
 function lock_wait($name, $delay = 30) {
-  $delay = (int) $delay;
-  while ($delay--) {
+  // Multiply delay by ten, to allow polling to begin at 1/10th of a second.
+  $delay = (int) $delay * 10;
+  $sleep = 100000;
+  while ($delay > 0) {
     // This function should only be called by a request that failed to get a
     // lock, so we sleep first to give the parallel request a chance to finish
     // and release the lock.
-    sleep(1);
+    usleep($sleep);
+    // After each sleep, double the value of $sleep, to reduce the potential
+    // for a lock stampede.
+    $delay = $delay - $sleep;
+    $sleep = min($sleep * 2, $delay);
     if (lock_may_be_available($name)) {
       // No longer need to wait.
       return FALSE;
