JAVA8 ConcurrentHashMap.computeIfAbsent 的使用及說明

1.簡述

在JAVA8的Map接口中,增加了一個方法computeIfAbsent,此方法簽名如下:


public V computeIfAbsent(K key, Function<? super K,? extends V> mappingFunction)


如果mappingFunction(key)返回的值爲null或拋出異常,則不會有記錄存入map
此方法首先判斷緩存MAP中是否存在指定key的值,如果不存在,會自動調用mappingFunction(key)計算key的value,然後將key = value放入到緩存Map。

 

ConcurrentHashMap中重寫了computeIfAbsent 方法確保mappingFunction中的操作是線程安全的。

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


2.使用方法

可以將原始代碼爲:


ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
Client esClient = map.get("key");
if (esClient == null) {
    //防止啓動時高併發導致創建多個client
    synchronized (ClientUtil.class) {
        esClient = map.get("key");
        if (esClient == null) {
            //初始化操作
            esClient = new Client();
        }
    }
}

 

 

使用computeIfAbsent 代替:


ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.computeIfAbsent("key",()->{
    //初始化操作
    return new Client();
});

注:mappingFunction方法的結果會作爲值直接插入到map中,無需在mappingFunction中再執行put。

3.風險

不能在ConcurrentHashMap.computeIfAbsent方法中進行遞歸調用,及在mappingFunction方法中不能在進行put相關的操作會造成死循環。

例如:


public static void main(String[] args) {
		Map<String, Integer> map = new ConcurrentHashMap<>(16);
		map.computeIfAbsent("AaAa", key -> {
			return map.computeIfAbsent("BBBB", key2 -> 42);
		});
 
		System.out.println(map.size());
	}


This is fixed in JDK 9 with JDK-8071667 . When the test case is run in JDK 9-ea, it gives a ConcurrentModification Exception.
https://bugs.openjdk.java.net/browse/JDK-8172951
好在這個問題在java 1.9中已經基本修復了。

java 9

 

 

引用:

https://blog.csdn.net/qq_16998379/article/details/90719502

https://www.imooc.com/article/68332

https://www.jianshu.com/p/59bd27e137e1

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章