You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@groovy.apache.org by su...@apache.org on 2020/07/11 06:03:09 UTC
[groovy] 02/02: Replace `ManagedConcurrentMap` with
`ManagedIdentityConcurrentMap`
This is an automated email from the ASF dual-hosted git repository.
sunlan pushed a commit to branch GROOVY-9631
in repository https://gitbox.apache.org/repos/asf/groovy.git
commit b32cbcac6bc6e67ea2316fe3efbf307f8ffdb814
Author: Daniel Sun <su...@apache.org>
AuthorDate: Sat Jul 11 14:02:19 2020 +0800
Replace `ManagedConcurrentMap` with `ManagedIdentityConcurrentMap`
---
.../java/org/codehaus/groovy/ast/ClassHelper.java | 4 +-
.../org/codehaus/groovy/reflection/ClassInfo.java | 6 +-
.../groovy/reflection/MixinInMetaClass.java | 19 ++-
.../metaclass/ThreadManagedMetaBeanProperty.java | 16 +--
.../groovy/util/ManagedIdentityConcurrentMap.java | 149 +++++++++++++++++++++
.../util/ManagedIdentityConcurrentMapTest.groovy | 63 +++++++++
6 files changed, 234 insertions(+), 23 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
index 17bcc8d..54b320f 100644
--- a/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
+++ b/src/main/java/org/codehaus/groovy/ast/ClassHelper.java
@@ -52,7 +52,7 @@ import org.codehaus.groovy.runtime.GeneratedClosure;
import org.codehaus.groovy.runtime.GeneratedLambda;
import org.codehaus.groovy.transform.stc.StaticTypeCheckingSupport;
import org.codehaus.groovy.transform.trait.Traits;
-import org.codehaus.groovy.util.ManagedConcurrentMap;
+import org.codehaus.groovy.util.ManagedIdentityConcurrentMap;
import org.codehaus.groovy.util.ReferenceBundle;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
import org.objectweb.asm.Opcodes;
@@ -408,7 +408,7 @@ public class ClassHelper {
}
static class ClassHelperCache {
- static ManagedConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle());
+ static ManagedIdentityConcurrentMap<Class, SoftReference<ClassNode>> classCache = new ManagedIdentityConcurrentMap<Class, SoftReference<ClassNode>>(ReferenceBundle.getWeakBundle());
}
public static boolean isSAMType(final ClassNode type) {
diff --git a/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java
index e55b2ed..b0c00e1 100644
--- a/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java
+++ b/src/main/java/org/codehaus/groovy/reflection/ClassInfo.java
@@ -46,7 +46,7 @@ import org.codehaus.groovy.util.Finalizable;
import org.codehaus.groovy.util.LazyReference;
import org.codehaus.groovy.util.LockableObject;
import org.codehaus.groovy.util.ManagedConcurrentLinkedQueue;
-import org.codehaus.groovy.util.ManagedConcurrentMap;
+import org.codehaus.groovy.util.ManagedIdentityConcurrentMap;
import org.codehaus.groovy.util.ManagedReference;
import org.codehaus.groovy.util.ReferenceBundle;
import org.codehaus.groovy.vmplugin.VMPluginFactory;
@@ -82,7 +82,7 @@ public class ClassInfo implements Finalizable {
private ManagedReference<MetaClass> weakMetaClass;
MetaMethod[] dgmMetaMethods = MetaMethod.EMPTY_ARRAY;
MetaMethod[] newMetaMethods = MetaMethod.EMPTY_ARRAY;
- private ManagedConcurrentMap<Object, MetaClass> perInstanceMetaClassMap;
+ private ManagedIdentityConcurrentMap<Object, MetaClass> perInstanceMetaClassMap;
private static final ReferenceBundle softBundle = ReferenceBundle.getSoftBundle();
private static final ReferenceBundle weakBundle = ReferenceBundle.getWeakBundle();
@@ -409,7 +409,7 @@ public class ClassInfo implements Finalizable {
if (metaClass != null) {
if (perInstanceMetaClassMap == null)
- perInstanceMetaClassMap = new ManagedConcurrentMap<Object, MetaClass>(ReferenceBundle.getWeakBundle());
+ perInstanceMetaClassMap = new ManagedIdentityConcurrentMap<Object, MetaClass>(ReferenceBundle.getWeakBundle());
perInstanceMetaClassMap.put(obj, metaClass);
}
diff --git a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
index afaf956..f9763e1 100644
--- a/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
+++ b/src/main/java/org/codehaus/groovy/reflection/MixinInMetaClass.java
@@ -32,7 +32,7 @@ import org.codehaus.groovy.runtime.metaclass.MixedInMetaClass;
import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaMethod;
import org.codehaus.groovy.runtime.metaclass.MixinInstanceMetaProperty;
import org.codehaus.groovy.runtime.metaclass.NewInstanceMetaMethod;
-import org.codehaus.groovy.util.ManagedConcurrentMap;
+import org.codehaus.groovy.util.ManagedIdentityConcurrentMap;
import org.codehaus.groovy.util.ReferenceBundle;
import java.lang.reflect.Modifier;
@@ -40,19 +40,18 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-public class MixinInMetaClass extends ManagedConcurrentMap {
+public class MixinInMetaClass {
final ExpandoMetaClass emc;
final CachedClass mixinClass;
final CachedConstructor constructor;
-
- private static final ReferenceBundle softBundle = ReferenceBundle.getSoftBundle();
+ private final ManagedIdentityConcurrentMap managedIdentityConcurrentMap =
+ new ManagedIdentityConcurrentMap(ReferenceBundle.getSoftBundle());
public MixinInMetaClass(ExpandoMetaClass emc, CachedClass mixinClass) {
- super(softBundle);
this.emc = emc;
this.mixinClass = mixinClass;
- constructor = findDefaultConstructor(mixinClass);
+ this.constructor = findDefaultConstructor(mixinClass);
emc.addMixinClass(this);
}
@@ -70,20 +69,20 @@ public class MixinInMetaClass extends ManagedConcurrentMap {
}
public synchronized Object getMixinInstance(Object object) {
- Object mixinInstance = get(object);
+ Object mixinInstance = managedIdentityConcurrentMap.get(object);
if (mixinInstance == null) {
mixinInstance = constructor.invoke(MetaClassHelper.EMPTY_ARRAY);
new MixedInMetaClass(mixinInstance, object);
- put(object, mixinInstance);
+ managedIdentityConcurrentMap.put(object, mixinInstance);
}
return mixinInstance;
}
public synchronized void setMixinInstance(Object object, Object mixinInstance) {
if (mixinInstance == null) {
- remove(object);
+ managedIdentityConcurrentMap.remove(object);
} else {
- put(object, mixinInstance);
+ managedIdentityConcurrentMap.put(object, mixinInstance);
}
}
diff --git a/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java b/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java
index 5074908..8cae32f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java
+++ b/src/main/java/org/codehaus/groovy/runtime/metaclass/ThreadManagedMetaBeanProperty.java
@@ -23,7 +23,7 @@ import groovy.lang.MetaBeanProperty;
import groovy.lang.MetaMethod;
import org.codehaus.groovy.reflection.CachedClass;
import org.codehaus.groovy.reflection.ReflectionCache;
-import org.codehaus.groovy.util.ManagedConcurrentMap;
+import org.codehaus.groovy.util.ManagedIdentityConcurrentMap;
import org.codehaus.groovy.util.ReferenceBundle;
import java.lang.reflect.Modifier;
@@ -39,9 +39,9 @@ import java.util.concurrent.ConcurrentHashMap;
* @since 1.5
*/
public class ThreadManagedMetaBeanProperty extends MetaBeanProperty {
- private static final ConcurrentHashMap<String,ManagedConcurrentMap> PROPNAME_TO_MAP = new ConcurrentHashMap<String, ManagedConcurrentMap>();
+ private static final ConcurrentHashMap<String,ManagedIdentityConcurrentMap> PROPNAME_TO_MAP = new ConcurrentHashMap<String, ManagedIdentityConcurrentMap>();
- private final ManagedConcurrentMap instance2Prop;
+ private final ManagedIdentityConcurrentMap instance2Prop;
private final Class declaringClass;
private final ThreadBoundGetter getter;
@@ -117,11 +117,11 @@ public class ThreadManagedMetaBeanProperty extends MetaBeanProperty {
instance2Prop = getInstance2PropName(name);
}
- private static ManagedConcurrentMap getInstance2PropName(String name) {
- ManagedConcurrentMap res = PROPNAME_TO_MAP.get(name);
+ private static ManagedIdentityConcurrentMap getInstance2PropName(String name) {
+ ManagedIdentityConcurrentMap res = PROPNAME_TO_MAP.get(name);
if (res == null) {
- res = new ManagedConcurrentMap(SOFT_BUNDLE);
- ManagedConcurrentMap ores = PROPNAME_TO_MAP.putIfAbsent(name, res);
+ res = new ManagedIdentityConcurrentMap(SOFT_BUNDLE);
+ ManagedIdentityConcurrentMap ores = PROPNAME_TO_MAP.putIfAbsent(name, res);
if (ores != null)
return ores;
}
@@ -176,7 +176,7 @@ public class ThreadManagedMetaBeanProperty extends MetaBeanProperty {
* @see groovy.lang.MetaMethod#invoke(java.lang.Object, java.lang.Object[])
*/
public Object invoke(Object object, Object[] arguments) {
- return instance2Prop.getOrPut(object, getInitialValue()).getValue();
+ return instance2Prop.getOrPut(object, getInitialValue());
}
}
diff --git a/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java b/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java
new file mode 100644
index 0000000..2736244
--- /dev/null
+++ b/src/main/java/org/codehaus/groovy/util/ManagedIdentityConcurrentMap.java
@@ -0,0 +1,149 @@
+/*
+ * 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.codehaus.groovy.util;
+
+import java.util.Collection;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This is a basic implementation of a map able to forget its keys. This
+ * map uses internally a ConcurrentHashMap, thus should be safe for concurrency.
+ * hashcode and equals are used to find the entries and should thus be implemented
+ * properly for the keys. This map does not support null keys.
+ *
+ * @param <K> the key type
+ * @param <V> the value type
+ * @since 4.0.0
+ */
+public class ManagedIdentityConcurrentMap<K, V> {
+ private final ConcurrentHashMap<ManagedReference<K>, V> internalMap;
+ private final ReferenceBundle bundle;
+
+ public ManagedIdentityConcurrentMap(ReferenceBundle bundle) {
+ this.internalMap = new ConcurrentHashMap<>();
+ this.bundle = bundle;
+ }
+
+ /**
+ * Returns the value stored for the given key at the point of call.
+ *
+ * @param key a non null key
+ * @return the value stored in the map for the given key
+ */
+ public V get(K key) {
+ return internalMap.get(new ManagedIdentityKey<K>(key));
+ }
+
+ /**
+ * Sets a new value for a given key. an older value is overwritten.
+ *
+ * @param key a non null key
+ * @param value the new value
+ */
+ public V put(final K key, V value) {
+ return internalMap.put(new ManagedIdentityKey<K>(key), value);
+ }
+
+ /**
+ * Returns the values of the map
+ */
+ public Collection<V> values() {
+ return internalMap.values();
+ }
+
+ /**
+ * Remove the key specified entry
+ *
+ * @param key the key to look up
+ * @return the removed value
+ */
+ public V remove(K key) {
+ return internalMap.remove(new ManagedIdentityKey<K>(key));
+ }
+
+ /**
+ * Get the key specified value, or put the default value and return it if the key is absent
+ *
+ * @param key the key to look up
+ * @param value the default value if the key is absent
+ * @return the value
+ */
+ public V getOrPut(K key, V value) {
+ return internalMap.computeIfAbsent(new ManagedIdentityKey<K>(key), k -> value);
+ }
+
+ /**
+ * Returns the map size
+ */
+ public Object size() {
+ return internalMap.size();
+ }
+
+ /**
+ * Check if the map is empty or not
+ *
+ * @return {@code true} when the map is empty, otherwise {@code false}
+ */
+ public boolean isEmpty() {
+ return internalMap.isEmpty();
+ }
+
+ /**
+ * Represents identity key of {@link ManagedIdentityConcurrentMap}
+ *
+ * @param <K> the key type
+ */
+ private class ManagedIdentityKey<K> extends ManagedReference<K> {
+ private final int hashCode;
+
+ private ManagedIdentityKey(K key) {
+ super(bundle, key);
+ this.hashCode = hash(key);
+ }
+
+ @Override
+ public void finalizeReference() {
+ internalMap.remove(this);
+ super.finalizeReference();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof ManagedIdentityKey)) return false;
+ return get() == ((ManagedIdentityKey<?>) o).get();
+ }
+
+ @Override
+ public int hashCode() {
+ return this.hashCode;
+ }
+
+ private int hash(K key) {
+ int h = (null == key) ? 0 : System.identityHashCode(key);
+ h += (h << 15) ^ 0xffffcd7d;
+ h ^= (h >>> 10);
+ h += (h << 3);
+ h ^= (h >>> 6);
+ h += (h << 2) + (h << 14);
+ h ^= (h >>> 16);
+ return h;
+ }
+ }
+}
diff --git a/src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy b/src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy
new file mode 100644
index 0000000..778f429
--- /dev/null
+++ b/src/test/org/codehaus/groovy/util/ManagedIdentityConcurrentMapTest.groovy
@@ -0,0 +1,63 @@
+/*
+ * 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.codehaus.groovy.util
+
+
+import org.junit.Test
+
+class ManagedIdentityConcurrentMapTest {
+ @Test
+ void testRemovingEntriesFromMapAfterGC() {
+ def m = new ManagedIdentityConcurrentMap<Object, String>(ReferenceBundle.getWeakBundle())
+ def k1 = new Object()
+ m.put(k1, "a")
+ def k2 = new Object()
+ m.put(k2, "b")
+ def k3 = new Object()
+ m.put(k3, "c")
+
+ assert 3 == m.size()
+
+ k1 = null // the related entry whose value is "a" should be removed after gc happened
+ for (int i = 0; i < 10; i++) {
+ System.gc()
+
+ if (m.values().size() < 3) {
+ break
+ }
+
+ Thread.sleep(100)
+ }
+ assert 2 == m.size() && ['b', 'c'] == m.values().sort()
+
+ k2 = null
+ k3 = null
+ for (int i = 0; i < 10; i++) {
+ System.gc()
+
+ if (m.values().size() == 0) {
+ break
+ }
+
+ Thread.sleep(100)
+ }
+
+ assert m.isEmpty()
+ }
+}