• JDK8 ConcurrentHashMap.computeIfAbsent 带来的问题


    死循环问题

    JDK8中的ConcurrentHashMap也不一定是安全的。
    官方Bug报告: https://bugs.openjdk.java.net/browse/JDK-8062841
    JDK9中变化内容: http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/6dd59c01f011
    参考demo

    package at.irian.misc.javabug;
    
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class Main {
        public static void main(String[] args) {
            Map<String, Integer> map = new ConcurrentHashMap<>(16);
            map.computeIfAbsent(
                    "AaAa",
                    key -> {
                        return map.computeIfAbsent(
                                "BBBB",
                                key2 -> 42);
                    }
            );
        }
    }
    

    问题在第一次调用AaAa时,会创建ReservationNode节点保存在map中
    第二次调用时,因为节点存在,而且是synchronized 可重入,会调用else后代码
    ReservationNode hash=-3 if elseif 都不会进入
    会导致循环调用

    public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
        if (key == null || mappingFunction == null)
            throw new NullPointerException();
        int h = spread(key.hashCode());
        V val = null;
        int binCount = 0;
        for (Node<K,V>[] tab = table;;) {
            Node<K,V> f; int n, i, fh;
            if (tab == null || (n = tab.length) == 0)
                tab = initTable();
            else if ((f = tabAt(tab, i = (n - 1) & h)) == null) {
                Node<K,V> r = new ReservationNode<K,V>();
                synchronized (r) {
                    if (casTabAt(tab, i, null, r)) {
                        binCount = 1;
                        Node<K,V> node = null;
                        try {
                            if ((val = mappingFunction.apply(key)) != null)
                                node = new Node<K,V>(h, key, val, null);
                        } finally {
                            setTabAt(tab, i, node);
                        }
                    }
                }
                if (binCount != 0)
                    break;
            }
            else if ((fh = f.hash) == MOVED)
                tab = helpTransfer(tab, f);
            else { 
                boolean added = false;
                synchronized (f) {
                    if (tabAt(tab, i) == f) {
                        if (fh >= 0) {
                            binCount = 1;
                            for (Node<K,V> e = f;; ++binCount) {
                                K ek; V ev;
                                if (e.hash == h &&
                                    ((ek = e.key) == key ||
                                     (ek != null && key.equals(ek)))) {
                                    val = e.val;
                                    break;
                                }
                                Node<K,V> pred = e;
                                if ((e = e.next) == null) {
                                    if ((val = mappingFunction.apply(key)) != null) {
                                        added = true;
                                        pred.next = new Node<K,V>(h, key, val, null);
                                    }
                                    break;
                                }
                            }
                        }
                        else if (f instanceof TreeBin) {
                            binCount = 2;
                            TreeBin<K,V> t = (TreeBin<K,V>)f;
                            TreeNode<K,V> r, p;
                            if ((r = t.root) != null &&
                                (p = r.findTreeNode(h, key, null)) != null)
                                val = p.val;
                            else if ((val = mappingFunction.apply(key)) != null) {
                                added = true;
                                t.putTreeVal(h, key, val);
                            }
                        }
                    }
                }
                if (binCount != 0) {
                    if (binCount >= TREEIFY_THRESHOLD)
                        treeifyBin(tab, i);
                    if (!added)
                        return val;
                    break;
                }
            }
        }
        if (val != null)
            addCount(1L, binCount);
        return val;
    }
    

    MyBatis 3.5.X遇上JDK8出现了性能问题

    主要是在并发调用ConcurrentHashMap.computeIfAbsent方法时,为保证原子性,对相同的key进行修改时,会造成线程阻塞。

    MyBatis 3.5.4不存在该问题,因为那是还没引进computeIfAbsent这个函数
    参考DefaultReflectorFactory.java

      @Override
      public Reflector findForClass(Class<?> type) {
        if (classCacheEnabled) {
                // synchronized (type) removed see issue #461
          Reflector cached = reflectorMap.get(type);
          if (cached == null) {
            cached = new Reflector(type);
            reflectorMap.put(type, cached);
          }
          return cached;
        } else {
          return new Reflector(type);
        }
      }
    

    目前MyBatis 3.5.7已修复该问题
    引进了一个工具类,临时修复了JDK-8161372这个issue
    JDK-8161372 issue中有并发测试computeIfAbsent的demo

    public class MapUtil {
      /**
       * A temporary workaround for Java 8 specific performance issue JDK-8161372 .<br>
       * This class should be removed once we drop Java 8 support.
       *
       * @see <a href="https://bugs.openjdk.java.net/browse/JDK-8161372">https://bugs.openjdk.java.net/browse/JDK-8161372</a>
       */
      public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
        V value = map.get(key);
        if (value != null) {
          return value;
        }
        return map.computeIfAbsent(key, mappingFunction::apply);
      }
    
      /**
       * Map.entry(key, value) alternative for Java 8.
       */
      public static <K, V> Entry<K, V> entry(K key, V value) {
        return new AbstractMap.SimpleImmutableEntry<>(key, value);
      }
    
      private MapUtil() {
        super();
      }
    }
    

    参考

    1. 不一样的内容:死磕JDK8中ConcurrentHashMap.computeIfAbsent 死循环 Bug
    2. 我慌了!MyBatis 3.5.X遇上JDK8竟然出现了性能问题,全项目组都得加班~
    3. ConcurrentHashMap.computeIfAbsent stuck in an endless loop
    4. ConcurrentHashMap.computeIfAbsent(k,f) locks bin when k present
    5. java8的ConcurrentHashMap的死循环问题
  • 相关阅读:
    国外保健品品牌介绍
    海淘第一单
    表查询语句与方法
    表与表关系
    表完整性约束
    表字段数据类型
    存储引擎
    数据库之MySQL基本操作
    MAC重置MySQL root 密码
    进程池、线程池、协程
  • 原文地址:https://www.cnblogs.com/zendwang/p/JDK8-ConcurrentHashMap-computeIfAbsent-an-endless-loop-bug.html
Copyright © 2020-2023  润新知