线程安全:使用CAS + synchronized
1.8中没有了Segment[]对象
修改 i的属性:
主内存中的值 = 预期值person.i,那么cpu就可以修改成功,修改为 person.i+1;
若当主内存中的值 != person.i时,那么就不会进行修改,会返回false。
一般会结合自旋的方式来使用cas。
while(true) { }
boolean b = UNSAFE.compareAndSwapInt(person, I_OFFSET, person.i, person.i+1);
1. put()
1.8中 ,concurrentHashMap只有一个Node[]数组:
putVal():
首先计算hash值,然后根据这个 hash & (n - 1),算出下标 i
Unsafe():通过Unsafa()方法从数组中获取第i个元素的值。
CAS自旋:new Node<K,V>(),通过cas的操作,将新的Node结点放到数组的第i个位置。如果cas成功,则 break。
若CAS失败,很有可能是由于其它的线程将第i个位置的元素赋值成功了;导致该线程在进行casTabAt()方法判断时,参数不为null,失败返回false。
1.1 ConcurrentHashMap的put方法
在put方法中调用了putVal方法,也是使用头插法
1.7中使用分段锁Segment,1.8中使用 synchronized(f)
当多个线程都准备插入元素,对f --> taable[i] 进行加锁,f对象表示Node[]数组中 一个结点(链表的头结点)。
加锁之后,需要重新检查 f结点 是否还是链表头结点
f结点是链表的结点,则插入链表中:
binCount:记录链表中元素个数
在遍历链表过程中,找到key相等的,则退出循环,返回oldValue。
若key都不相等,则遍历到链表尾,尾插法。然后判断结点个数是否大于阈值,是否树化。
若 结点f 是 TreeBin类型,则为树的一个结点;
TreeBin对象代表一棵红黑树,TreeBin红黑树内部的根节点在插入过程中发生变化,不影响Node[]数组
Tree里面有TreeNode对象:
treeifyBin(Node<K,V>[] tab, int index):树化
tab数组中index 下的链表进行树化,方法中加锁 synchronized
双向链表,hd表示双向链表的头结点
初始化 initTable()
Thread.yield():当前线程放弃cpu,进行下一次cpu资源的争夺
1.8 concurrentHashMap扩容