UTILS-16
authorErik Brakkee <erik@brakkee.org>
Sat, 12 Oct 2013 12:08:18 +0000 (14:08 +0200)
committerErik Brakkee <erik@brakkee.org>
Sat, 12 Oct 2013 12:08:18 +0000 (14:08 +0200)
CachedObject gets out of sync when multiple CachedObjects share the same cache

The cached object now uses the non-null value obtained from the cache instead of the locally cached value in case of a cache hit. The locally cached value is only used when computing the new value fails in case of a cache miss.

support/general/src/main/java/org/wamblee/cache/CachedObject.java

index da86298d6598115f9f5b4a683a27053c8f08da73..70baf67ca9c298c6de19ea0a2967c6a6b184e876 100644 (file)
@@ -23,13 +23,11 @@ import java.util.logging.Logger;
  * Represents a cached object identified by the key it has in a certain
  * {@link Cache}. The object is either retrieved from the cache if the cache has
  * it, or a call back is invoked to get the object (and put it in the cache).
- * 
+ *
  * @author Erik Brakkee
- * 
  */
 public class CachedObject<KeyType extends Serializable, ValueType extends Serializable> {
-    private static final Logger LOGGER = Logger.getLogger(CachedObject.class
-        .getName());
+    private static final Logger LOGGER = Logger.getLogger(CachedObject.class.getName());
 
     /**
      * Cache to use.
@@ -42,7 +40,7 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
     private KeyType objectKey;
 
     /**
-     * Last known value.
+     * Last known value. We only use this to return the last known value in case recomputation of the value fails.
      */
     private ValueType value;
 
@@ -58,17 +56,14 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
 
     /**
      * Constructs the cached object.
-     * 
-     * @param aCache
-     *            Cache to use.
-     * @param aObjectKey
-     *            Key of the object in the cache.
-     * @param aComputation
-     *            Computation to get the object in case the object is not in the
-     *            cache.
+     *
+     * @param aCache       Cache to use.
+     * @param aObjectKey   Key of the object in the cache.
+     * @param aComputation Computation to get the object in case the object is not in the
+     *                     cache.
      */
     public CachedObject(Cache<KeyType, ValueType> aCache, KeyType aObjectKey,
-        Computation<KeyType, ValueType> aComputation) {
+            Computation<KeyType, ValueType> aComputation) {
         cache = aCache;
         objectKey = aObjectKey;
         computation = aComputation;
@@ -77,13 +72,13 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
     /**
      * Gets the object. Since the object is cached, different calls to this
      * method may return different objects.
-     * 
+     * <p/>
      * If the object is expired from the cache it is recomputed using the
      * callback. In case the callback throws an exception the last known value
      * is used. In case an exception is thrown, the problem is also logged. In
      * case a recomputation is already being done by another thread, the last
      * known value is immediately returned.
-     * 
+     *
      * @return Object.
      */
     public ValueType get() {
@@ -98,7 +93,9 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
                 // expired
                 computing = true;
             } else {
-                // no change.
+                // Two different instances of cached object might share the same cache and so it can occur
+                // that the value in one of the instances it out of date.
+                value = cachedValue;
                 return value;
             }
         }
@@ -113,9 +110,9 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
             synchronized (this) {
                 value = object;
             }
-        } catch (Exception e) {
-            LOGGER.log(Level.INFO,
-                "Recomputation of cached item failed for key '" + objectKey +
+        }
+        catch (Exception e) {
+            LOGGER.log(Level.INFO, "Recomputation of cached item failed for key '" + objectKey +
                     "'", e);
         } finally {
             synchronized (this) {
@@ -137,7 +134,7 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
 
     /**
      * Gets the cache.
-     * 
+     *
      * @return Cache.
      */
     public Cache getCache() {
@@ -146,19 +143,16 @@ public class CachedObject<KeyType extends Serializable, ValueType extends Serial
 
     /**
      * Callback invoked to compute an object if it was not found in the cache.
-     * 
-     * @param <T>
-     *            Type of the object
+     *
+     * @param <T> Type of the object
      */
     public static interface Computation<Key extends Serializable, Value extends Serializable> {
         /**
          * Gets the object. Called when the object is not in the cache. In case
          * computation fails, an exception should be thrown to ensure that the
          * last known value will be used.
-         * 
-         * @param aObjectKey
-         *            Id of the object in the cache.
-         * 
+         *
+         * @param aObjectKey Id of the object in the cache.
          * @return Object, must be non-null.
          */
         Value getObject(Key aObjectKey) throws Exception;