Repository: groovy
Updated Branches:
  refs/heads/master b88379785 -> 6617e30e9


Refine caches and add the atomic `getAndPut` method to `LRUCache`


Project: http://git-wip-us.apache.org/repos/asf/groovy/repo
Commit: http://git-wip-us.apache.org/repos/asf/groovy/commit/6617e30e
Tree: http://git-wip-us.apache.org/repos/asf/groovy/tree/6617e30e
Diff: http://git-wip-us.apache.org/repos/asf/groovy/diff/6617e30e

Branch: refs/heads/master
Commit: 6617e30e958884ed9f9b8743520e4ea872ce9266
Parents: b883797
Author: sunlan <[email protected]>
Authored: Mon Jan 8 16:18:58 2018 +0800
Committer: sunlan <[email protected]>
Committed: Mon Jan 8 16:18:58 2018 +0800

----------------------------------------------------------------------
 src/main/groovy/groovy/util/ProxyGenerator.java | 16 ++++----
 .../org/apache/groovy/util/ObjectHolder.java    | 42 ++++++++++++++++++++
 .../ConcurrentLinkedHashMap.java                | 27 +++----------
 .../groovy/runtime/memoize/CommonCache.java     |  6 +--
 .../runtime/memoize/ConcurrentCommonCache.java  |  6 +--
 .../groovy/runtime/memoize/EvictableCache.java  | 20 ----------
 .../groovy/runtime/memoize/LRUCache.java        | 32 +++++++++++----
 .../groovy/runtime/memoize/Memoize.java         | 15 +++----
 .../groovy/runtime/memoize/MemoizeCache.java    | 32 +++++++++++++++
 9 files changed, 128 insertions(+), 68 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/groovy/groovy/util/ProxyGenerator.java
----------------------------------------------------------------------
diff --git a/src/main/groovy/groovy/util/ProxyGenerator.java 
b/src/main/groovy/groovy/util/ProxyGenerator.java
index ff4c1d5..8f165f7 100644
--- a/src/main/groovy/groovy/util/ProxyGenerator.java
+++ b/src/main/groovy/groovy/util/ProxyGenerator.java
@@ -228,13 +228,15 @@ public class ProxyGenerator {
         }
         boolean useDelegate = null != delegateClass;
         CacheKey key = new CacheKey(base, useDelegate ? delegateClass : 
Object.class, keys, intfs, emptyMethods, useDelegate);
-        ProxyGeneratorAdapter adapter = (ProxyGeneratorAdapter) 
adapterCache.get(key);
-        if (adapter == null) {
-            adapter = new ProxyGeneratorAdapter(closureMap, base, intfs, 
useDelegate ? delegateClass.getClassLoader() : base.getClassLoader(), 
emptyMethods, useDelegate ? delegateClass : null);
-            adapterCache.put(key, adapter);
-        }
-
-        return adapter;
+        final Class b = base;
+
+        return (ProxyGeneratorAdapter) adapterCache.getAndPut(
+                key,
+                k -> new ProxyGeneratorAdapter(closureMap, b, intfs, 
useDelegate
+                        ? delegateClass.getClassLoader()
+                        : b.getClassLoader(), emptyMethods, useDelegate ? 
delegateClass : null
+                )
+        );
     }
 
     private static void setMetaClass(final MetaClass metaClass) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/apache/groovy/util/ObjectHolder.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/groovy/util/ObjectHolder.java 
b/src/main/java/org/apache/groovy/util/ObjectHolder.java
new file mode 100644
index 0000000..9a39582
--- /dev/null
+++ b/src/main/java/org/apache/groovy/util/ObjectHolder.java
@@ -0,0 +1,42 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+package org.apache.groovy.util;
+
+/**
+ * Just hold an object
+ * @param <T> the type of object
+ */
+public class ObjectHolder<T> {
+    private T object;
+
+    public ObjectHolder() {}
+
+    public ObjectHolder(T object) {
+        this.object = object;
+    }
+
+    public T getObject() {
+        return object;
+    }
+
+    public void setObject(T object) {
+        this.object = object;
+    }
+}

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/apache/groovy/util/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/groovy/util/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java
 
b/src/main/java/org/apache/groovy/util/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java
index 0828e9b..5275b27 100644
--- 
a/src/main/java/org/apache/groovy/util/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java
+++ 
b/src/main/java/org/apache/groovy/util/concurrentlinkedhashmap/ConcurrentLinkedHashMap.java
@@ -15,6 +15,8 @@
  */
 package org.apache.groovy.util.concurrentlinkedhashmap;
 
+import org.apache.groovy.util.ObjectHolder;
+
 import javax.annotation.concurrent.GuardedBy;
 import javax.annotation.concurrent.Immutable;
 import javax.annotation.concurrent.ThreadSafe;
@@ -757,7 +759,7 @@ public final class ConcurrentLinkedHashMap<K, V> extends 
AbstractMap<K, V>
   V compute(final K key, final Function<? super K, ? extends V> 
mappingFunction, boolean onlyIfAbsent) {
     checkNotNull(key);
 
-    final NodeHolder<K, V> nh = new NodeHolder<K, V>();
+    final ObjectHolder<Node<K, V>> objectHolder = new ObjectHolder<Node<K, 
V>>();
 
     for (;;) {
       Function<K, Node<K, V>> f = k -> {
@@ -769,16 +771,16 @@ public final class ConcurrentLinkedHashMap<K, V> extends 
AbstractMap<K, V>
         final WeightedValue<V> weightedValue = new WeightedValue<V>(value, 
weight);
         final Node<K, V> node = new Node<K, V>(key, weightedValue);
 
-        nh.setNode(node);
+        objectHolder.setObject(node);
 
         return node;
       };
       Node<K, V> prior = data.computeIfAbsent(key, f);
 
-      Node<K, V> node = nh.getNode();
+      Node<K, V> node = objectHolder.getObject();
       if (null == node) {
         f.apply(key);
-        node = nh.getNode();
+        node = objectHolder.getObject();
       } else {
         // the return value of `computeIfAbsent` is different from the one of 
`putIfAbsent`.
         // if the key is absent in map, the return value of `computeIfAbsent` 
is the newly computed value, but `putIfAbsent` return null.
@@ -814,23 +816,6 @@ public final class ConcurrentLinkedHashMap<K, V> extends 
AbstractMap<K, V>
     }
   }
 
-  private static class NodeHolder<K, V> {
-    private Node<K, V> node;
-
-    public NodeHolder() {}
-
-    public NodeHolder(Node<K, V> node) {
-      this.node = node;
-    }
-
-    public Node<K, V> getNode() {
-      return node;
-    }
-
-    public void setNode(Node<K, V> node) {
-      this.node = node;
-    }
-  }
 
   @Override
   public V remove(Object key) {

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/CommonCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/memoize/CommonCache.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/CommonCache.java
index 4659459..6012181 100644
--- a/src/main/java/org/codehaus/groovy/runtime/memoize/CommonCache.java
+++ b/src/main/java/org/codehaus/groovy/runtime/memoize/CommonCache.java
@@ -74,7 +74,7 @@ public class CommonCache<K, V> implements EvictableCache<K, 
V> {
 
     /**
      * Constructs a LRU cache with the specified initial capacity and max size.
-     * The LRU cache is slower than {@link LRUCache} but will not put same 
value multi-times concurrently
+     * The LRU cache is slower than {@link LRUCache}
      * @param initialCapacity initial capacity of the LRU cache
      * @param maxSize max size of the LRU cache
      */
@@ -119,11 +119,11 @@ public class CommonCache<K, V> implements 
EvictableCache<K, V> {
      * {@inheritDoc}
      */
     @Override
-    public V getAndPut(K key, ValueProvider<K, V> valueProvider) {
+    public V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider) {
         return getAndPut(key, valueProvider, true);
     }
 
-    public V getAndPut(K key, ValueProvider<K, V> valueProvider, boolean 
shouldCache) {
+    public V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider, boolean shouldCache) {
         V value = get(key);
         if (null != value) {
             return value;

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java
index 9eae7cd..24ae6c3 100644
--- 
a/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java
+++ 
b/src/main/java/org/codehaus/groovy/runtime/memoize/ConcurrentCommonCache.java
@@ -54,7 +54,7 @@ public class ConcurrentCommonCache<K, V> extends 
CommonCache<K, V> {
 
     /**
      * Constructs a LRU cache with the specified initial capacity and max size.
-     * The LRU cache is slower than {@link LRUCache} but will not put same 
value multi-times concurrently
+     * The LRU cache is slower than {@link LRUCache}
      * @param initialCapacity initial capacity of the LRU cache
      * @param maxSize max size of the LRU cache
      */
@@ -109,12 +109,12 @@ public class ConcurrentCommonCache<K, V> extends 
CommonCache<K, V> {
      * {@inheritDoc}
      */
     @Override
-    public V getAndPut(K key, ValueProvider<K, V> valueProvider) {
+    public V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider) {
         return getAndPut(key, valueProvider, true);
     }
 
     @Override
-    public V getAndPut(K key, ValueProvider<K, V> valueProvider, boolean 
shouldCache) {
+    public V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider, boolean shouldCache) {
         V value;
 
         readLock.lock();

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/EvictableCache.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/memoize/EvictableCache.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/EvictableCache.java
index 60bf8fd..1cd45a8 100644
--- a/src/main/java/org/codehaus/groovy/runtime/memoize/EvictableCache.java
+++ b/src/main/java/org/codehaus/groovy/runtime/memoize/EvictableCache.java
@@ -44,14 +44,6 @@ public interface EvictableCache<K, V> extends 
MemoizeCache<K, V> {
     Map<K, V> clear();
 
     /**
-     * Try to get the value from cache.
-     * If not found, create the value by {@link ValueProvider} and put it into 
the cache, at last return the value
-     * @param key
-     * @return the cached value
-     */
-    V getAndPut(K key, ValueProvider<K, V> valueProvider);
-
-    /**
      * Get all cached values
      * @return all cached values
      */
@@ -77,18 +69,6 @@ public interface EvictableCache<K, V> extends 
MemoizeCache<K, V> {
     int size();
 
     /**
-     * Represents a provider used to create value
-     * @param <K> type of the key
-     * @param <V> type of the value
-     */
-    interface ValueProvider<K, V> {
-        /**
-         * Provide the created value
-         */
-        V provide(K key);
-    }
-
-    /**
      * Represents a eviction strategy for the cache with limited size
      */
     enum EvictionStrategy {

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/LRUCache.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/memoize/LRUCache.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/LRUCache.java
index bf041ae..3600df2 100644
--- a/src/main/java/org/codehaus/groovy/runtime/memoize/LRUCache.java
+++ b/src/main/java/org/codehaus/groovy/runtime/memoize/LRUCache.java
@@ -23,6 +23,7 @@ import 
org.apache.groovy.util.concurrentlinkedhashmap.ConcurrentLinkedHashMap;
 import java.lang.ref.SoftReference;
 import java.util.Iterator;
 import java.util.Map;
+import java.util.concurrent.ConcurrentMap;
 
 /**
  * A cache backed by a ConcurrentLinkedHashMap
@@ -30,32 +31,49 @@ import java.util.Map;
  * @author Vaclav Pech
  * @author <a href="mailto:[email protected]";>Daniel.Sun</a>
  */
-public final class LRUCache implements MemoizeCache<Object, Object> {
-    private final Map<Object, Object> cache;
+public final class LRUCache<K, V> implements MemoizeCache<K, V> {
+    private final ConcurrentMap<K, V> cache;
 
     public LRUCache(final int maxCacheSize) {
 //        cache = Collections.synchronizedMap(new 
LRUProtectionStorage(maxCacheSize));
-        cache = new ConcurrentLinkedHashMap.Builder<>()
+        cache = new ConcurrentLinkedHashMap.Builder<K, V>()
                 .maximumWeightedCapacity(maxCacheSize)
                 .build();
     }
 
-    public Object put(final Object key, final Object value) {
+    @Override
+    public V put(final K key, final V value) {
         return cache.put(key, value);
     }
 
-    public Object get(final Object key) {
+    @Override
+    public V get(final K key) {
         return cache.get(key);
     }
 
     /**
+     * Try to get the value from cache.
+     * If not found, create the value by {@link ValueProvider} and put it into 
the cache, at last return the value.
+     *
+     * The operation is completed atomically.
+     *
+     * @param key
+     * @param valueProvider provide the value if the associated value not found
+     * @return
+     */
+    @Override
+    public V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider) {
+        return cache.computeIfAbsent(key, k -> valueProvider.provide(k));
+    }
+
+    /**
      * Remove all entries holding SoftReferences to gc-evicted objects.
      */
     public void cleanUpNullReferences() {
         synchronized (cache) {
-            final Iterator<Map.Entry<Object, Object>> iterator = 
cache.entrySet().iterator();
+            final Iterator<Map.Entry<K, V>> iterator = 
cache.entrySet().iterator();
             while (iterator.hasNext()) {
-                final Map.Entry<Object, Object> entry = iterator.next();
+                final Map.Entry<K, V> entry = iterator.next();
                 final Object value = entry.getValue();
 
                 if (!(value instanceof SoftReference)) continue;

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/Memoize.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/codehaus/groovy/runtime/memoize/Memoize.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/Memoize.java
index c4dd3d9..f5d600e 100644
--- a/src/main/java/org/codehaus/groovy/runtime/memoize/Memoize.java
+++ b/src/main/java/org/codehaus/groovy/runtime/memoize/Memoize.java
@@ -127,14 +127,15 @@ public abstract class Memoize {
             maximumNumberOfParameters = closure.getMaximumNumberOfParameters();
         }
         
-        @Override public V call(final Object... args) {
+        @Override
+        public V call(final Object... args) {
             final Object key = generateKey(args);
-            Object result = cache.get(key);
-            if (result == null) {
-                result = closure.call(args);
+            Object result = cache.getAndPut(key, k -> {
+                Object r = closure.call(args);
                 //noinspection GroovyConditionalCanBeElvis
-                cache.put(key, result != null ? result : MEMOIZE_NULL);
-            }
+                return r != null ? r : MEMOIZE_NULL;
+            });
+
             return result == MEMOIZE_NULL ? null : (V) result;
         }
 
@@ -153,7 +154,7 @@ public abstract class Memoize {
             this.lruProtectionStorage = lruProtectionStorage;
             this.queue = queue;
         }
-        
+
         @Override public V call(final Object... args) {
             if (queue.poll() != null) cleanUpNullReferences(cache, queue);  // 
if something has been evicted, do a clean-up
             final Object key = generateKey(args);

http://git-wip-us.apache.org/repos/asf/groovy/blob/6617e30e/src/main/java/org/codehaus/groovy/runtime/memoize/MemoizeCache.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/codehaus/groovy/runtime/memoize/MemoizeCache.java 
b/src/main/java/org/codehaus/groovy/runtime/memoize/MemoizeCache.java
index aa0eb8e..0efbdda 100644
--- a/src/main/java/org/codehaus/groovy/runtime/memoize/MemoizeCache.java
+++ b/src/main/java/org/codehaus/groovy/runtime/memoize/MemoizeCache.java
@@ -43,9 +43,41 @@ public interface MemoizeCache<K, V> {
     V get(K key);
 
     /**
+     * Try to get the value from cache.
+     * If not found, create the value by {@link ValueProvider} and put it into 
the cache, at last return the value.
+     *
+     * @param key
+     * @param valueProvider provide the value if the associated value not found
+     * @return the cached value
+     */
+    default V getAndPut(K key, ValueProvider<? super K, ? extends V> 
valueProvider) {
+        V value = this.get(key);
+
+        if (null == value) {
+            value = valueProvider.provide(key);
+            this.put(key, value);
+        }
+
+        return value;
+    }
+
+    /**
      * Invoked when some of the held SoftReferences have been evicted by the 
garbage collector and so should be removed from the cache.
      * The implementation must ensure that concurrent invocations of all 
methods on the cache may occur from other threads
      * and thus should protect any shared resources.
      */
     void cleanUpNullReferences();
+
+    /**
+     * Represents a provider used to create value
+     * @param <K> type of the key
+     * @param <V> type of the value
+     */
+    @FunctionalInterface
+    interface ValueProvider<K, V> {
+        /**
+         * Provide the created value
+         */
+        V provide(K key);
+    }
 }

Reply via email to