JDK 1.8 的JUC对CurrentHashMap 重新定义后做了很大的变革。我们一点一点来拆解,今天就先说说他的 InitTable 方法
上代码:
ConcurrentHashMap的initTable 初始化方法关于何时初始化我们后面说Put的时候在讨论,今天就先说说这个InitTable
并发包是如何做到既保证并发安全又保证高性能的呢?
关键知识点:
1、CAS
2、volatile
代码中的高亮部分标记了sizeCtl。
sizeCtl是何物?
看图:
参数sizeCtl Unsafe的初始化sizeCtl 默认为0,用来控制table的初始化和扩容操作
如果sizeCtl 为-1 则说明正在初始化
-N 表示有N-1个线程正在进行扩容操作
注意:图二中SIZECTL中获取的 sizeCtl的地址偏移值,是在static中初始化的。
get到了关键信息,我们先放一边,继续说init方法
if ((sc =sizeCtl) <0)
Thread.yield(); // lost initialization race; just spin
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1))
..... //后续省略
第一步:
是判断SizeCtl是不是<0 判断是否正在初始化。如果是那就Thread.yield() 实则就只允许一个线程操作,是个自选的操作
第二步:
U.compareAndSwapInt(this, SIZECTL, sc, -1) 这个cas的判断地址并操作为-1
unsafe方法中的Cas 判断了地址偏移,(SIZECTL早在static就以初始化好了)
如果比较为True 那就更新为-1。原子操作保证了安全。(不明白CAS的移步百度查询Unsafe的Cas)
同时volatile保证了顺序与内存可见性。
总结:在第一步进行判断,是不能保证并发安全的,如果两个线程同时进入,就需要Cas去保证安全,并且原子变更数值
当然sizeCtl 不仅仅在init中使用,还在扩容中使用。纵观整个类会发现大量的Unsafe的方法。虽然官方并不推荐使用,
但是事实证明 Doug Lea 是你大爷,还是你大爷。
网友评论