From 2fc8ec060dd2042cc4ab22ab60ed8accedf1e903 Mon Sep 17 00:00:00 2001 From: Iwao AVE! Date: Fri, 9 Apr 2021 16:55:18 +0900 Subject: [PATCH] Prevent thread from being blocked by JDK-8 ConcurrentHashMap#computeIfAbsent() This is a workaround for JDK-8161372 https://github.com/mybatis/mybatis-3/issues/1929 https://groups.google.com/g/mybatis-user/c/iBTN98c0dP4/m/H6IJS-LzAQAJ We should drop this workaround once we drop Java 8 support or the fix is backported. --- .../apache/ibatis/binding/MapperProxy.java | 13 ++---- .../cache/TransactionalCacheManager.java | 5 ++- .../executor/keygen/Jdbc3KeyGenerator.java | 5 ++- .../resultset/DefaultResultSetHandler.java | 5 ++- .../java/org/apache/ibatis/plugin/Plugin.java | 5 ++- .../reflection/DefaultReflectorFactory.java | 6 ++- .../apache/ibatis/reflection/Reflector.java | 5 ++- .../scripting/LanguageDriverRegistry.java | 6 ++- .../java/org/apache/ibatis/util/MapUtil.java | 40 +++++++++++++++++++ 9 files changed, 66 insertions(+), 24 deletions(-) create mode 100644 src/main/java/org/apache/ibatis/util/MapUtil.java diff --git a/src/main/java/org/apache/ibatis/binding/MapperProxy.java b/src/main/java/org/apache/ibatis/binding/MapperProxy.java index 2fd67270353..4c9dce3d4e9 100644 --- a/src/main/java/org/apache/ibatis/binding/MapperProxy.java +++ b/src/main/java/org/apache/ibatis/binding/MapperProxy.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import org.apache.ibatis.reflection.ExceptionUtil; import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.util.MapUtil; /** * @author Clinton Begin @@ -91,15 +92,7 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl private MapperMethodInvoker cachedInvoker(Method method) throws Throwable { try { - // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372 - // It should be removed once the fix is backported to Java 8 or - // MyBatis drops Java 8 support. See gh-1929 - MapperMethodInvoker invoker = methodCache.get(method); - if (invoker != null) { - return invoker; - } - - return methodCache.computeIfAbsent(method, m -> { + return MapUtil.computeIfAbsent(methodCache, method, m -> { if (m.isDefault()) { try { if (privateLookupInMethod == null) { diff --git a/src/main/java/org/apache/ibatis/cache/TransactionalCacheManager.java b/src/main/java/org/apache/ibatis/cache/TransactionalCacheManager.java index 99cee78f78f..512a451a067 100644 --- a/src/main/java/org/apache/ibatis/cache/TransactionalCacheManager.java +++ b/src/main/java/org/apache/ibatis/cache/TransactionalCacheManager.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2019 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.Map; import org.apache.ibatis.cache.decorators.TransactionalCache; +import org.apache.ibatis.util.MapUtil; /** * @author Clinton Begin @@ -52,7 +53,7 @@ public void rollback() { } private TransactionalCache getTransactionalCache(Cache cache) { - return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new); + return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new); } } diff --git a/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java b/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java index 4f1b816eda0..d5c52499ff2 100644 --- a/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java +++ b/src/main/java/org/apache/ibatis/executor/keygen/Jdbc3KeyGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,7 @@ import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; +import org.apache.ibatis.util.MapUtil; /** * @author Clinton Begin @@ -156,7 +157,7 @@ private void assignKeysToParamMap(Configuration configuration, ResultSet rs, Res for (int i = 0; i < keyProperties.length; i++) { Entry entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, true); - Entry, List> iteratorPair = assignerMap.computeIfAbsent(entry.getKey(), + Entry, List> iteratorPair = MapUtil.computeIfAbsent(assignerMap, entry.getKey(), k -> entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>())); iteratorPair.getValue().add(entry.getValue()); } diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java index 5e5461b3b71..bb2dcffad77 100644 --- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java +++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,6 +61,7 @@ import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; +import org.apache.ibatis.util.MapUtil; /** * @author Clinton Begin @@ -589,7 +590,7 @@ private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, PendingRelation deferLoad = new PendingRelation(); deferLoad.metaObject = metaResultObject; deferLoad.propertyMapping = parentMapping; - List relations = pendingRelations.computeIfAbsent(cacheKey, k -> new ArrayList<>()); + List relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>()); // issue #255 relations.add(deferLoad); ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet()); diff --git a/src/main/java/org/apache/ibatis/plugin/Plugin.java b/src/main/java/org/apache/ibatis/plugin/Plugin.java index 61e951de5b2..e4485d65f32 100644 --- a/src/main/java/org/apache/ibatis/plugin/Plugin.java +++ b/src/main/java/org/apache/ibatis/plugin/Plugin.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,7 @@ import java.util.Set; import org.apache.ibatis.reflection.ExceptionUtil; +import org.apache.ibatis.util.MapUtil; /** * @author Clinton Begin @@ -75,7 +76,7 @@ private static Map, Set> getSignatureMap(Interceptor intercepto Signature[] sigs = interceptsAnnotation.value(); Map, Set> signatureMap = new HashMap<>(); for (Signature sig : sigs) { - Set methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>()); + Set methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>()); try { Method method = sig.type().getMethod(sig.method(), sig.args()); methods.add(method); diff --git a/src/main/java/org/apache/ibatis/reflection/DefaultReflectorFactory.java b/src/main/java/org/apache/ibatis/reflection/DefaultReflectorFactory.java index 95d3ce480db..3ba2b98e0c2 100644 --- a/src/main/java/org/apache/ibatis/reflection/DefaultReflectorFactory.java +++ b/src/main/java/org/apache/ibatis/reflection/DefaultReflectorFactory.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2019 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import org.apache.ibatis.util.MapUtil; + public class DefaultReflectorFactory implements ReflectorFactory { private boolean classCacheEnabled = true; private final ConcurrentMap, Reflector> reflectorMap = new ConcurrentHashMap<>(); @@ -39,7 +41,7 @@ public void setClassCacheEnabled(boolean classCacheEnabled) { public Reflector findForClass(Class type) { if (classCacheEnabled) { // synchronized (type) removed see issue #461 - return reflectorMap.computeIfAbsent(type, Reflector::new); + return MapUtil.computeIfAbsent(reflectorMap, type, Reflector::new); } else { return new Reflector(type); } diff --git a/src/main/java/org/apache/ibatis/reflection/Reflector.java b/src/main/java/org/apache/ibatis/reflection/Reflector.java index 230518fe1e8..b9149965ce9 100644 --- a/src/main/java/org/apache/ibatis/reflection/Reflector.java +++ b/src/main/java/org/apache/ibatis/reflection/Reflector.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2020 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,6 +40,7 @@ import org.apache.ibatis.reflection.invoker.MethodInvoker; import org.apache.ibatis.reflection.invoker.SetFieldInvoker; import org.apache.ibatis.reflection.property.PropertyNamer; +import org.apache.ibatis.util.MapUtil; /** * This class represents a cached set of class definition information that @@ -143,7 +144,7 @@ private void addSetMethods(Class clazz) { private void addMethodConflict(Map> conflictingMethods, String name, Method method) { if (isValidPropertyName(name)) { - List list = conflictingMethods.computeIfAbsent(name, k -> new ArrayList<>()); + List list = MapUtil.computeIfAbsent(conflictingMethods, name, k -> new ArrayList<>()); list.add(method); } } diff --git a/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java b/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java index f84d7b64928..af324bf3f0d 100644 --- a/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java +++ b/src/main/java/org/apache/ibatis/scripting/LanguageDriverRegistry.java @@ -1,5 +1,5 @@ /** - * Copyright 2009-2019 the original author or authors. + * Copyright 2009-2021 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,8 @@ import java.util.HashMap; import java.util.Map; +import org.apache.ibatis.util.MapUtil; + /** * @author Frank D. Martinez [mnesarco] */ @@ -31,7 +33,7 @@ public void register(Class cls) { if (cls == null) { throw new IllegalArgumentException("null is not a valid Language Driver"); } - LANGUAGE_DRIVER_MAP.computeIfAbsent(cls, k -> { + MapUtil.computeIfAbsent(LANGUAGE_DRIVER_MAP, cls, k -> { try { return k.getDeclaredConstructor().newInstance(); } catch (Exception ex) { diff --git a/src/main/java/org/apache/ibatis/util/MapUtil.java b/src/main/java/org/apache/ibatis/util/MapUtil.java new file mode 100644 index 00000000000..146f5fbfe4b --- /dev/null +++ b/src/main/java/org/apache/ibatis/util/MapUtil.java @@ -0,0 +1,40 @@ +/** + * Copyright 2009-2021 the original author or authors. + * + * Licensed 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.ibatis.util; + +import java.util.Map; +import java.util.function.Function; + +public class MapUtil { + /** + * A temporary workaround for Java 8 specific performance issue JDK-8161372 .
+ * This class should be removed once we drop Java 8 support. + * + * @see https://bugs.openjdk.java.net/browse/JDK-8161372 + */ + public static V computeIfAbsent(Map map, K key, Function mappingFunction) { + V value = map.get(key); + if (value != null) { + return value; + } + return map.computeIfAbsent(key, mappingFunction::apply); + } + + private MapUtil() { + super(); + } +}