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); + } }
