Explorar o código

Merge pull request #5399 from Codeprh/feture/ConcurrentMap/performance

fix: CollectionUtils#computeIf lock bug
qmdx hai 1 ano
pai
achega
84cb51f3e7

+ 47 - 4
mybatis-plus-core/src/main/java/com/baomidou/mybatisplus/core/toolkit/CollectionUtils.java

@@ -15,7 +15,15 @@
  */
 package com.baomidou.mybatisplus.core.toolkit;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
 import java.util.function.Function;
 
 /**
@@ -28,6 +36,18 @@ public class CollectionUtils {
 
     private static final int MAX_POWER_OF_TWO = 1 << (Integer.SIZE - 2);
 
+    private static boolean isJdk8;
+
+    static {
+        // Java 8
+        // Java 9+: 9,11,17
+        try {
+            isJdk8 = System.getProperty("java.version").startsWith("1.8.");
+        } catch (Exception ignore) {
+            isJdk8 = true;
+        }
+    }
+
     /**
      * 校验集合是否为空
      *
@@ -99,6 +119,9 @@ public class CollectionUtils {
      * 用来过渡下Jdk1.8下ConcurrentHashMap的性能bug
      * https://bugs.openjdk.java.net/browse/JDK-8161372
      *
+     *  A temporary workaround for Java 8 ConcurrentHashMap#computeIfAbsent specific performance issue: JDK-8161372.</br>
+     *  @see <a href="https://bugs.openjdk.java.net/browse/JDK-8161372">https://bugs.openjdk.java.net/browse/JDK-8161372</a>
+     *
      * @param concurrentHashMap ConcurrentHashMap 没限制类型了,非ConcurrentHashMap就别调用这方法了
      * @param key               key
      * @param mappingFunction   function
@@ -108,11 +131,31 @@ public class CollectionUtils {
      * @since 3.4.0
      */
     public static <K, V> V computeIfAbsent(Map<K, V> concurrentHashMap, K key, Function<? super K, ? extends V> mappingFunction) {
-        V v = concurrentHashMap.get(key);
-        if (v != null) {
+        Objects.requireNonNull(mappingFunction);
+        if (isJdk8) {
+            V v = concurrentHashMap.get(key);
+            if (null == v) {
+                // issue#11986 lock bug
+                // v = map.computeIfAbsent(key, func);
+
+                // this bug fix methods maybe cause `func.apply` multiple calls.
+                v = mappingFunction.apply(key);
+                if (null == v) {
+                    return null;
+                }
+                final V res = concurrentHashMap.putIfAbsent(key, v);
+                if (null != res) {
+                    // if pre value present, means other thread put value already, and putIfAbsent not effect
+                    // return exist value
+                    return res;
+                }
+                // if pre value is null, means putIfAbsent effected, return current value
+            }
             return v;
+        } else {
+            return concurrentHashMap.computeIfAbsent(key, mappingFunction);
         }
-        return concurrentHashMap.computeIfAbsent(key, mappingFunction);
+
     }
 
     /**

+ 10 - 0
mybatis-plus-core/src/test/java/com/baomidou/mybatisplus/test/toolkit/CollectionUtilsTest.java

@@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test;
 import java.lang.reflect.Field;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * @author nieqiuqiu 2020/7/2
@@ -38,6 +39,8 @@ class CollectionUtilsTest {
         map = newHashMap(16);
         Assertions.assertEquals(16, getTableSize(map));
         Assertions.assertEquals(12, getThresholdValue(map));
+
+        computeIfAbsent();
     }
 
     private Map<String, String> newHashMapWithExpectedSize(int size) {
@@ -65,4 +68,11 @@ class CollectionUtilsTest {
         return (int) field.get(map);
     }
 
+    private void computeIfAbsent() {
+
+        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
+        CollectionUtils.computeIfAbsent(map, "AaAa", key -> map.computeIfAbsent("BBBB", key2 -> "noah"));
+        System.out.println("i can return");
+    }
+
 }