https://www.cnblogs.com/slwenyi/p/6393808.html
https://www.cnblogs.com/study-everyday/p/6430462.html#autoid-2-0-0
内容包括:部分加锁机制,读写操作,容器的扩容。
concurrentHashMap分为1.7版本与1.8版本
1.7版本使用的是Segment与hashEntry,segment继承了reentrantLock,通过segment加锁实现互斥修改,而读segment不上锁,通过hapend-befor来保证写操作先于读操作发生。在堆segment进行初始化的时候使用了CAS,通过Unsafe类的native方法操作内存,由于native方法是系统级别的,执行过程不会中断,所以unsafe的方法不存在同时判断出旧值未修改,一定会有先后之分。
transient volatile HashEntry[] table;
数据结构:table数组+单向链表
1.8版本将加锁粒度更细化(减少了冲突发生的概率),通过violatile 关键字修饰的Node(类似hashEntry)与synchronized,CAS实现同步。当node链表超过阈值,Node会更改为TreeBin对象,封装了TreeNode红黑树。对Node的初始化依然使用了CAS,而对于hash冲突则使用synchronized同步。
数据结构:table数组+单向链表+红黑树(logn)
为什么1.8使用同步而不是互斥锁,粒度更小的互斥锁会产生大量锁对象,而且hash冲突的概率并没有那么大,冲突概率小的时候使用synchronized性能是比较高的。
为什么不直接全程用CAS机制,既然unsafe类的系统方法能够保证先后顺序。
前提是你的操作冲突了之后需要重新申请资源。如果同时执行put操作,没获取到操作的线程,要么阻塞,要么自旋,而自旋就像一个while死循环一样,是很浪费cpu资源的,要产生阻塞就必定要使用synchronized关键字或者lock锁,所以concurrentHashMap只在初始化的时候用了CAS机制,因为这样只自旋了一次。
悲观锁:比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁:比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。
总结:两种所各有优缺点,读取频繁使用乐观锁,写入频繁使用悲观锁。
---------------------
作者:zhiboer
来源:CSDN
原文:https://blog.csdn.net/claram/article/details/53959367
版权声明:本文为博主原创文章,转载请附上博文链接!
网友评论