搜索
房产
装修
汽车
婚嫁
健康
理财
旅游
美食
跳蚤
二手房
租房
招聘
二手车
教育
茶座
我要买房
买东西
装修家居
交友
职场
生活
网购
亲子
情感
龙城车友
找美食
谈婚论嫁
美女
兴趣
八卦
宠物
手机

ConcurrentHashMap如何保证线程安全

[复制链接]
查看: 17|回复: 0

1万

主题

1万

帖子

4万

积分

管理员

Rank: 9Rank: 9Rank: 9

积分
45261
发表于 2020-2-14 17:25 | 显示全部楼层 |阅读模式
HashMap的put,get,size等方式都不是线程平安的,而HashTable固然保证了线程平安,但却是用了服从极低的方式,在put,get,size等方式上加上了synchronized,这就致使全数的并发进程都要合作同一把锁,一个线程在举行同步操纵时,其他线程都需要等待。
为了保证聚集的线程平安性,Jdk供给了同步包装器,比如说Collections.synchronizedMap,不外它们都是操纵粗粒度的同步方式,高并发情况下性能较差。
可以看看Collections.synchronizedMap的实现。
ConcurrentHashMap如何保证线程安全  热点新闻 image-cef2c861e3294784891d3ecc4e4d7664

ConcurrentHashMap如何保证线程安全  热点新闻 image-5f96b3c44bd746ebbcc09cf95c5bb74c

与并发平安有关的代码都操纵了synchronized关键字举行了修饰,操纵的锁mutex可以在机关方式中看到就是this。
固然说不在是用synchronized来修饰方式,可是照旧操纵了this做mutex锁,治标不治标。
加倍普遍的挑选是操纵并发包供给的线程平安容器类,它供给了各类并发容器,比如ConcurrentHashMap、CopyOnWriteArrayList。
各类线程平安行列,如ArrayBlockingQueue、SynchronousQueue。
ConcurrentHashMap分析

ConcurrentHashMap和HashMap一样,在Java8时结构上发生了很大变化。
之前的ConcurrentHashMap的实现是基于分手锁,也就是内部举行分段,每一个段中都具有HashEntry数组,hashcode类似的key也是依照链表的方式存储的。
ConcurrentHashMap如何保证线程安全  热点新闻 image-3b66bc8d0ffc4d0f87f321f4ac8f20f5

HashEntry内部操纵volatile修饰的value字段来保证可见性,也操纵了不成变工具的机制以改良操纵Unsafe供给的底层本事,比如volatile access,去间接完成部分操纵,以最优化性能,究竟Unsafe中的很多操纵都是JVM intrinsic优化过的。
Segment的初始巨细由DEFAULT_CONCURRENCY_LEVEL来肯定,默许是16,在机关方式中可以自界说巨细,不外必须是2的幂,假如输入7,会自动建立8。
ConcurrentHashMap如何保证线程安全  热点新闻 image-2934f6bd73d64b35800a8d1e14e1143d

对于get方式来说,只需要保证可见性,无需其他同步操纵。
关键是put方式,首先是经过二次哈希制止哈希辩说,然后以Unsafe挪用方式,间接获得响应的Segment,然后举行线程平安的put操纵。
  1. public V put(K key, V value) {    Segment s;    if (value == null)        throw new NullPointerException();    // 二次哈希,以保证数据的分离性,制止哈希辩说    int hash = hash(key.hashCode());    int j = (hash >>> segmentShift) & segmentMask;    if ((s = (Segment)UNSAFE.getObject // nonvolatile; recheck    (segments, (j = TREEIFY_THRESHOLD)                    treeifyBin(tab, i);                if (oldVal != null)                    return oldVal;                break;            }        }    }    addCount(1L, binCount);    return null;}
复制代码
具体流程以下:

  • 按照key盘算出hashcode 。
  • 判定能否需要举行初始化initTable。
  • f即为当前key定位出的Node,假如为空表现当前位置可以写入数据,操纵 CAS 实行写入,失利则自旋保证乐成。
  • 假如当前位置的 hashcode == MOVED == -1,则需要举行扩容。
  • 假如都不满足,则操纵 synchronized 锁写入数据。
  • 假如数目大于 TREEIFY_THRESHOLD 则要转换为红黑树。
可以发现1.8今后的锁的颗粒度,是加在链表头上的,这是一个很垂危的冲破。
1.8中不依靠与segment加锁,segment数目与桶数目同等。
首先判定容器能否为空,为空则举行初始化操纵volatile的sizeCtl作为互斥本事,假如发现合作性的初始化,就停息在何处,等待条件规复,否则操纵CAS设备排他标志(U.compareAndSwapInt(this, SIZECTL, sc, -1)),否则重试
对key hash盘算获得该key寄存的桶位置,判定该桶能否为空,为空则操纵CAS设备新节点,否则操纵synchronize加锁,遍历桶中数据,更换或新增加点到桶中。末端判定能否需要转为红黑树,转换之前判定能否需要扩容。
末端来看看1.8 ConcurrentHashMap的size方式实现。
ConcurrentHashMap如何保证线程安全  热点新闻 image-8c0185c9373341b0af15860173e97211

ConcurrentHashMap如何保证线程安全  热点新闻 image-4a39bac625844753973370bd3577fd4c

在sumCount方式中操纵到了CounterCell,对于CounterCell的操纵,是基于java.util.concurrent.atomic.LongAdder举行的,是一种JVM操纵空间更调更高服从的方式,操纵了Striped64内部的复杂逻辑。
  1. // 一种用于分派计数的加添单元。改编自LongAdder和Striped64。请检察他们的内部文档举行表白。@sun.misc.Contended static final class CounterCell {    volatile long value;    CounterCell(long x) { value = x; }}
复制代码
免责声明:假如加害了您的权益,请联系站长,我们会实时删除侵权内容,感谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2006-2014 全椒百姓网-全椒知名**,发布及时新鲜的全椒新闻资讯 生活信息 版权所有 法律顾问:高律师 客服电话:0791-88289918
技术支持:迪恩网络科技公司  Powered by Discuz! X3.2
快速回复 返回顶部 返回列表