目录
【jdk 1.7】
jdk 1.7中 ConcurrentHashMap的数据结构是:ReentrantLock+Segment+HashEntry
Segment数组的意义就是将一个大的table分割成多个小的table来进行加锁,保证了数据安全,而每一个Segment元素存储的是 数组+链表,这个和HashMap的数据存储结构一样;
当我们去put数据的时候,经过两次hashcode操作,去定位存储的位置;
1.第一次根据key计算hashcode值,锁定我要存储的Segment;
2.第二次根据key计算hashcode值,锁定我要存储的hashMap的数组位置
Segment实现了ReentrantLock(乐观锁),也就带有锁的功能,保证了数据安全;
当我们去get数据的时候,也是经过两次hashcode操作,
1.第一次根据key计算hashcode值,锁定我要取值的Segment
2.第二次根据key计算hashcode值,锁定我要取值的hashMap的数组位置
然后,定位查找的位置,遍历HashEntry中链表进行equals对比查询;
这样操作保证了数据安全,但是也存在一些弊端:
当某个Segment下面的数据量【桶】越来越大时,对应的查询效率和更新效率会变低;
当某个Segment进行加锁时,修改某个桶中的数据,这个Segment下面其他桶的数据就都被加锁。
就会导致更新效率变低,最终导致我们并发量变低。
【jdk 1.8】
jdk 1.8 中,ConcurrentHashMap的数据结构是:
Node数组 + 链表 + 红黑树 + synchronized + CAS;
取消了segment 分段锁,也就意味着不需要进行二次hash,就可以找到对应的Node了;
而Node 是 ConcurrentHashMap存储结构的基本单元,继承了HashMap中的Entry,用于存储数据;
put操作
根据key进行hashCode值计算,找到对应的Node,如果为空表示当前位置可以写入数据,利用CAS尝试写入,失败则自旋保证成功,如果非空,则利用synchronized锁写入数据
这样操作的好处是:
优化了底层数据结构,降低了锁的粒度,仅仅使用一次hash就能获取到桶的位置,提高了执行和并行效率,提高吞吐量;
【CAS】
【自旋锁】
CAS 比较和替换 compare and swap
是乐观锁思想的一种实现方式,JUC中很多工具类都是基于CAS的
简单描述:
当你去修改数据时,我认为数据是没有被修改过的,我直接尝试执行修改操作,
当真正修改数据时,才去比较我们的数据有没有发生变化,
如果发生变化,修改失败,如果没有发生变化,直接修改成功
【这样的好处就是:无锁操作,没有加锁的逻辑,执行效率更高,消耗的内存更少】
为什么会有不相等的情况?
这是因为在修改之前,别人线程进行修改了
(单线程不会出现这种问题)
【预期值】:从内存中拿到的值
【内存值】:内存中存储的值【可能会被其他线程修改】
步骤:
1--先从内存中获取数据,拿到预期值
2--我直接尝试修改
3--修改的时候比较内存值和预期值
4--如果相等,直接进行修改;
不相等,自选获取最新的内存值,作为预期值,再次尝试修改,直到修改成功。
并非这里真的有一把锁,而是用了一些逻辑,实现了一种类似于锁的功能