You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by ju...@apache.org on 2010/10/04 15:04:59 UTC

svn commit: r1004224 - /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java

Author: jukka
Date: Mon Oct  4 13:04:59 2010
New Revision: 1004224

URL: http://svn.apache.org/viewvc?rev=1004224&view=rev
Log:
JCR-2699: Improve read/write concurrency

Use segments also in ItemStateReferenceCache to make synchronization more fine-grained

Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java?rev=1004224&r1=1004223&r2=1004224&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/state/ItemStateReferenceCache.java Mon Oct  4 13:04:59 2010
@@ -18,11 +18,11 @@ package org.apache.jackrabbit.core.state
 
 import org.apache.commons.collections.map.ReferenceMap;
 import org.apache.jackrabbit.core.id.ItemId;
-import org.apache.jackrabbit.core.util.Dumpable;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -43,36 +43,27 @@ import java.util.Map;
  * </ul>
  * This implementation of ItemStateCache is thread-safe.
  */
-public class ItemStateReferenceCache implements ItemStateCache, Dumpable {
+public class ItemStateReferenceCache implements ItemStateCache {
 
     /** Logger instance */
     private static Logger log = LoggerFactory.getLogger(ItemStateReferenceCache.class);
 
     /**
-     * primary cache storing weak references to <code>ItemState</code>
-     * instances.
+     * Cache that automatically flushes entries based on some eviction policy;
+     * entries flushed from the secondary cache will be indirectly flushed
+     * from the reference map by the garbage collector if they thus are
+     * rendered weakly reachable.
      */
-    @SuppressWarnings("unchecked")
-    private final Map<ItemId, ItemState> refs =
-        // I tried using soft instead of weak references here, but that
-        // seems to have some unexpected performance consequences (notable
-        // increase in the JCR TCK run time). So even though soft references
-        // are generally recommended over weak references for caching
-        // purposes, it seems that using weak references is safer here.
-        new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+    private final ItemStateCache cache;
 
     /**
-     * secondary cache that automatically flushes entries based on some
-     * eviction policy; entries flushed from the secondary cache will be
-     * indirectly flushed from the primary (reference) cache by the garbage
-     * collector if they thus are rendered weakly reachable.
+     * Segments of the weak reference map used to keep track of item states.
      */
-    private final ItemStateCache cache;
+    private final Map<ItemId, ItemState>[] segments;
 
     /**
      * Creates a new <code>ItemStateReferenceCache</code> that uses a
-     * <code>MLRUItemStateCache</code> instance as internal secondary
-     * cache.
+     * <code>MLRUItemStateCache</code> instance as internal cache.
      */
     public ItemStateReferenceCache(ItemStateCacheFactory cacheFactory) {
         this(cacheFactory.newItemStateCache());
@@ -85,95 +76,143 @@ public class ItemStateReferenceCache imp
      *
      * @param cache secondary cache implementing a custom eviction policy
      */
+    @SuppressWarnings("unchecked")
     public ItemStateReferenceCache(ItemStateCache cache) {
         this.cache = cache;
+        this.segments = new Map[Runtime.getRuntime().availableProcessors()];
+        for (int i = 0; i < segments.length; i++) {
+            // I tried using soft instead of weak references here, but that
+            // seems to have some unexpected performance consequences (notable
+            // increase in the JCR TCK run time). So even though soft references
+            // are generally recommended over weak references for caching
+            // purposes, it seems that using weak references is safer here.
+            segments[i] =
+                new ReferenceMap(ReferenceMap.HARD, ReferenceMap.WEAK);
+        }
+    }
+
+    /**
+     * Returns the reference map segment for the given entry key. The segment
+     * is selected based on the hash code of the key, after a transformation
+     * to prevent interfering with the optimal performance of the segment
+     * hash map.
+     *
+     * @param id item identifer
+     * @return reference map segment
+     */
+    private Map<ItemId, ItemState> getSegment(ItemId id) {
+        // Unsigned shift right to prevent negative indexes and to
+        // prevent too similar keys to all get stored in the same segment
+        return segments[(id.hashCode() >>> 1) % segments.length];
     }
 
     //-------------------------------------------------------< ItemStateCache >
     /**
      * {@inheritDoc}
      */
-    public synchronized boolean isCached(ItemId id) {
-        // check primary cache
-        return refs.containsKey(id);
+    public boolean isCached(ItemId id) {
+        Map<ItemId, ItemState> segment = getSegment(id);
+        synchronized (segment) {
+            return segment.containsKey(id);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized ItemState retrieve(ItemId id) {
-        // fake call to update stats of secondary cache
-        cache.retrieve(id);
+    public ItemState retrieve(ItemId id) {
+        // Update the access statistics in the cache
+        ItemState state = cache.retrieve(id);
+        if (state != null) {
+            // Return fast to avoid the second lookup below
+            return state;
+        }
 
-        // retrieve from primary cache
-        return refs.get(id);
+        Map<ItemId, ItemState> segment = getSegment(id);
+        synchronized (segment) {
+            return segment.get(id);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized ItemState[] retrieveAll() {
-        // values of primary cache
-        return (ItemState[]) refs.values().toArray(new ItemState[refs.size()]);
+    public ItemState[] retrieveAll() {
+        List<ItemState> states = new ArrayList<ItemState>();
+        for (int i = 0; i < segments.length; i++) {
+            synchronized (segments[i]) {
+                states.addAll(segments[i].values());
+            }
+        }
+        return states.toArray(new ItemState[states.size()]);
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized void cache(ItemState state) {
-        ItemId id = state.getId();
-        if (refs.containsKey(id)) {
-            log.warn("overwriting cached entry " + id);
-        }
-        // fake call to update stats of secondary cache
+    public void cache(ItemState state) {
+        // Update the cache
         cache.cache(state);
-        // store weak reference in primary cache
-        refs.put(id, state);
 
+        // Store a weak reference in the reference map
+        ItemId id = state.getId();
+        Map<ItemId, ItemState> segment = getSegment(id);
+        synchronized (segment) {
+            if (segment.containsKey(id)) {
+                log.warn("overwriting cached entry " + id);
+            }
+            segment.put(id, state);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized void evict(ItemId id) {
-        // fake call to update stats of secondary cache
+    public void evict(ItemId id) {
+        // Update the cache
         cache.evict(id);
-        // remove from primary cache
-        refs.remove(id);
+        // Remove from reference map
+        // TODO: Allow the weak reference to be cleared automatically?
+        Map<ItemId, ItemState> segment = getSegment(id);
+        synchronized (segment) {
+            segment.remove(id);
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized void dispose() {
+    public void dispose() {
         cache.dispose();
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized void evictAll() {
-        // fake call to update stats of secondary cache
+    public void evictAll() {
+        // Update the cache
         cache.evictAll();
-        // remove all weak references from primary cache
-        refs.clear();
+        // remove all weak references from reference map
+        // TODO: Allow the weak reference to be cleared automatically?
+        for (int i = 0; i < segments.length; i++) {
+            synchronized (segments[i]) {
+                segments[i].clear();
+            }
+        }
     }
 
     /**
      * {@inheritDoc}
      */
-    public synchronized boolean isEmpty() {
-        // check primary cache
-        return refs.isEmpty();
+    public boolean isEmpty() {
+        for (int i = 0; i < segments.length; i++) {
+            synchronized (segments[i]) {
+                if (!segments[i].isEmpty()) {
+                    return false;
+                }
+            }
+        }
+        return true;
     }
 
-    //-------------------------------------------------------------< Dumpable >
-    /**
-     * {@inheritDoc}
-     */
-    public synchronized void dump(PrintStream ps) {
-        ps.println("ItemStateReferenceCache (" + this + ")");
-        ps.println("  refs: " + refs.keySet());
-        ps.println();
-    }
 }