深入分析ConcurrentHashMap
来源:互联网 发布:多媒体电子教室软件 编辑:程序博客网 时间:2024/05/16 08:36
再多线程的情况下,如果使用HashMap,就会导致死循环,导致cpu利用率接近100%,所以如果是并发的情况不要使用HashMap
导致死循环主要是这段代码,当在多线程的情况由于没有同步导致,着段代码在扩容的时候会执行
do { Entry<K,V> next = e.next; //假设线程一执行到这里就被调度挂起了,当再次获得执行的时候, 数据结构已经改变了,而线程却不知道 int i = indexFor(e.hash, newCapacity); e.next = newTable[i]; newTable[i] = e; e = next; } while (e != null);
线程安全的HashTable 容器
HashTable安全是安全,但是代价太大,该容器采用synchronized来保证线程安全,在多线程同时对它进行操作,竞争激烈的情况下效率非常的底下。因为当一个线程访问的时候,其他线程在轮询或阻塞的状态。如线程1使用put进行添加元素,线程2不但不能使用put方法添加元素,并且也不能使用get方法来获取元素,所以竞争越激烈效率越低。
并发集合容器ConcurrentHashMap
ConcurrentHashMap 里面的table 是实现 Map.Entry接口的一个数组,在对ConcurrentHashMap,进行put操作的时候,如果里面没有
键值对,则进行添加。
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { if (casTabAt(tab, i, null, new Node<K,V>(hash, key, value, null))) break; // no lock when adding to empty bin }
这里的casTabAt,以及tabAt本质使用的是sun.misc.Unsafe这里类里面的方法,该方法是在不加锁的情况下通过CAS实现线程安全
如果里面有存在想通过的key,那么就对其进行更新,在更新的代码块中,使用synchronized 来进行同步
synchronized (f) { if (tabAt(tab, i) == f) { if (fh >= 0) { binCount = 1; for (Node<K,V> e = f;; ++binCount) { K ek; if (e.hash == hash && ((ek = e.key) == key || (ek != null && key.equals(ek)))) { oldVal = e.val; if (!onlyIfAbsent) e.val = value; break; } ..... } } .... } }
刚才我们说HashMap 在重新分配空间的时候会导致死循环,那么ConcurrentHashMap是如何解决这个问题的了?结果跟我们上面分析的put
操作如出一辙
else if ((f = tabAt(tab, i)) == null) advance = casTabAt(tab, i, null, fwd); else if ((fh = f.hash) == MOVED) advance = true; // already processed else { synchronized (f) { ... }
使用了CAS,synchronized进行同步,这样就保证了线程的安全
说完put,那么get 操作又是如何保证同步的了?
if ((tab = table) != null && (n = tab.length) > 0 && (e = tabAt(tab, (n - 1) & h)) != null) { if ((eh = e.hash) == h) { if ((ek = e.key) == key || (ek != null && key.equals(ek))) return e.val; } else if (eh < 0) return (p = e.find(h, key)) != null ? p.val : null; while ((e = e.next) != null) { if (e.hash == h && ((ek = e.key) == key || (ek != null && key.equals(ek)))) return e.val; } }
get的实现相对于来说简单了,先经过一次再哈希,然后使用这个哈希值通过哈希运算定位到table,从代码上面看到就是使用一个 tabAt操作而并没有使用synchronized来对代码块进行同步,是因为在存储
value 的时候使用了volatile
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; volatile V val; .... }
定义成volatile的变量,能够在线程之间保持可见性,能够被多线程同时读,并且保证不会读到过期的值但是只能被单线程写(有一种情况可以被多线程写,就是写入的值不依赖于原值)之所以不会读到过期的值,是根据java内存模型的happen before原则,对volatile字段的写入操作先于读操作,即使两个线程同时修改和获取volatile变量,get操作也能拿到最新的值,这是用volatile替换锁的经典应用场景。
参考资料:
1.JDK1.8源代码。
2.《Java并发编程实践》
3.聊聊并发
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- ConcurrentHashMap深入分析
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- ConcurrentHashMap深入分析
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- ConcurrentHashMap深入分析
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 深入分析ConcurrentHashMap
- 五子棋AI算法第五篇-算杀
- VS2010中,无法嵌入互操作类型“……”,请改用适用的接口的解决方法
- 网络请求参数加签处理
- 《Linux操作系统分析》之分析计算机如何启动以及如何工作运行
- Mybatis3.x与Spring4.x整合
- 深入分析ConcurrentHashMap
- 极光推送的使用
- 100. Same Tree
- 数据的简单操作集
- 调用系统的的组件
- 《证券投资分析》结课论文
- fig搭建mediawiki
- Struts2 访问web元素
- 史上最全的iOS面试题及答案