• 什么是ConcurrentHashMap?


    目录

    ConcurrentHashMap是如何保证线程安全的?底层是如何实现的?

    JDK1.7如何实现?

    JDK1.8如何实现?

    JDK1.8ConcurrentHashMap是如何实现线程安全的?

    总结


    ConcurrentHashMap,它是HashMap高并发的版本,线程安全。

    hashmap在并发操作时会出现各种问题,比如死循环问题、数据覆盖[??]等问题,使用ConcurrentHashMap就可以进行解决。

    ConcurrentHashMap是如何保证线程安全的?底层是如何实现的?

    JDK1.7如何实现?

    在jdk1.7中,它采用数组+链表的方式进行实现,而数组又分为大数组Segment和小数组HashEntry,【大数组Segment【部分,分割】可以理解为Mysql中的数据库,而每个数据库(Segment)中又有很多张表HashEntry,每个HashEntry中又有很多条数据,这些数据是采用链表的方式进行连接的,然后加锁是通过给Segment添加ReentrantLock可重入锁来实现线程安全的,如图

    JDK1.8如何实现?

    而JDK1.8过后,采用了数组+链表+红黑树的方式优化了ConcurrentHashMap的实现,具体实现方式为才链表长度大于8,并且数组长度大于64时,链表就会升级成红黑树的结构(查询效率就会更高)

    JDK1.8ConcurrentHashMap是如何实现线程安全的?

    当调用put方法时,会使用的CAS+Volatile【不稳定的】或synchronized的方式来保证线程安全的,核心源码如下,

    1. public V put(K key, V value) {
    2. return putVal(key, value, false);
    3. }
    4. /** Implementation for put and putIfAbsent */
    5. final V putVal(K key, V value, boolean onlyIfAbsent) {
    6. if (key == null || value == null) throw new NullPointerException();
    7. int hash = spread(key.hashCode());
    8. int binCount = 0;
    9. for (Node[] tab = table;;) {
    10. Node f; int n, i, fh;
    11. if (tab == null || (n = tab.length) == 0)
    12. tab = initTable();
    13. else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
    14. if (casTabAt(tab, i, null,
    15. new Node(hash, key, value, null)))
    16. break; // no lock when adding to empty bin
    17. }
    18. else if ((fh = f.hash) == MOVED)
    19. tab = helpTransfer(tab, f);
    20. else {
    21. V oldVal = null;
    22. synchronized (f) {
    23. if (tabAt(tab, i) == f) {
    24. if (fh >= 0) {
    25. binCount = 1;
    26. for (Node e = f;; ++binCount) {
    27. K ek;
    28. if (e.hash == hash &&
    29. ((ek = e.key) == key ||
    30. (ek != null && key.equals(ek)))) {
    31. oldVal = e.val;
    32. if (!onlyIfAbsent)
    33. e.val = value;
    34. break;
    35. }
    36. Node pred = e;
    37. if ((e = e.next) == null) {
    38. pred.next = new Node(hash, key,
    39. value, null);
    40. break;
    41. }
    42. }
    43. }
    44. else if (f instanceof TreeBin) {
    45. Node p;
    46. binCount = 2;
    47. if ((p = ((TreeBin)f).putTreeVal(hash, key,
    48. value)) != null) {
    49. oldVal = p.val;
    50. if (!onlyIfAbsent)
    51. p.val = value;
    52. }
    53. }
    54. }
    55. }
    56. if (binCount != 0) {
    57. if (binCount >= TREEIFY_THRESHOLD)
    58. treeifyBin(tab, i);
    59. if (oldVal != null)
    60. return oldVal;
    61. break;
    62. }
    63. }
    64. }
    65. addCount(1L, binCount);
    66. return null;
    67. }

    从源码可以看出,添加元素首先会判断容器是否为空,如果为空则使用volatile加CAS来初始化

    如果容器不为空,则根据需要存储的元素计算出在容器该位置中是否为空,如果为空,则利用【乐观锁】CAS设置该节点,如果不为空则使用synchronized加锁,遍历桶table中的数据,替换或新增节点到桶中,最后判断是否需要转换成红黑树【判断链表长度和数组长度】,这样就能保证并发的访问时,线程是安全的了。

    总结

    ConcurrentHashMap是在头节点加锁保证线程安全的,锁的粒度【就是锁的代码更少了】相比segment来说更小了,发送锁冲突和加锁的频率降低了。并发操作时,执行效率就更高了。

    JDK1.8使用的红黑树结构优化了之前的链表结构,当数据量比较大的时候,查询效率就得到了很大的提升,时间复杂度从之前的O(n)优化到了O(logn)

  • 相关阅读:
    好的内容回复区
    计算机毕业设计Python+Django的学生考勤管理系统(源码+系统+mysql数据库+Lw文档)
    五 RPM方式Jenkins环境搭建
    Hugging Face 与 Wiz Research 合作提高人工智能安全性
    (免费分享)java基于SSM的进销存管理系统设计与实现
    3.1.2 内存池的实现与场景分析
    【python学习】标准库之文件目录访问-os.path方法和pathlib库的背景、功能、用法、代码示例和总结
    Selenium 是什么?简单了解Selenium
    Vue 项目的记录(六)
    黑马程序员Git笔记
  • 原文地址:https://blog.csdn.net/m0_64210833/article/details/126294540