的操作,线程A中V = 1,E = 1,N = 2;线程B中 V = 1,E = 1,N = 2;假设线程A先执行完整个操作,此时线程A发现 V = E = 1,所以线程A将N的值赋值给V,那么此时i的值就变成了 2 ;线程B随后也完成了操作,向主存中读取i的值,此时E = 2,V = 1,V != E,发现两个并不相等,说明i已经被其他线程修改了,因此不执行更新操作,而是从新读取V的值V = 2 ,执行+1后N = 3,完成后再读取主存中i的值,因为此时没有其他线程修改i的值了,所以E = 2,V = E = 2,两个值相等,因此执行赋值操作,将N的值赋值给i,最终得到的结果为3。在整过过程中始终没有使用到锁,却实现的线程的安全性。
从上面的过程知道,cas会面临着两个问题,一个是当线程一直更新不成功的话,那么这个线程就一直处于死循环中,这样会非常耗费cpu的资源;另一种是ABA的问题,即对i =1进行+1操作后,再-1,那么此时i的值仍为1,而另外一个线程获取的E的值也是1,认为其他线程没有修改过i,然后进行的更新操作,事实上已经有其他线程修改过了这个值了,这个就是 A ---> B ---> A 的问题;
4.3、获取table对应的索引元素的位置
通过(n-1)& hash 的算法来获得对应的table的下标的位置,如果对于这条公式不是很理解的同学可以到: jdk1.8源码分析-hashMap 博客中了解。
tabAt(Node<K,V>[] tab, int i): 这个方法使用了java提供的原子操作的类来操作的,sun.misc.Unsafe.getObjectVolatile 的方法来保证每次线程都能获取到最新的值;
casTabAt(Node<K,V>[] tab, int i,Node<K,V> c, Node<K,V> v): 这个方法是通过cas的方式来获取i位置的元素;
4.4、扩容
- 如果新增节点之后,所在的链表的元素个数大于等于8,则会调用treeifyBin把链表转换为红黑树。在转换结构时,若tab的长度小于MIN_TREEIFY_CAPACITY,默认值为64,
则会将数组长度扩大到原来的两倍,并触发transfer,重新调整节点位置。(只有当tab.length >= 64, ConcurrentHashMap才会使用红黑树。)
- 新增节点后,addCount统计tab中的节点个数大于阈值(sizeCtl),会触发transfer,重新调整节点位置。
/**
* Adds to count, and if table is too small and not already
* resizing, initiates transfer. If already resizing, helps
* perform transfer if work is available. Rechecks occupancy
* after a transfer to see if another resize is already needed
* because resizings are lagging additions.
*
* @param x the count to add
* @param check if <0, don't check resize, if <= 1 only check if uncontended
*/
private final void addCount(long x, int check) {
CounterCell[] as; long b, s;
if ((as = counterCells) != null ||
!U.compareAndSwapLong(this, BASECOUNT, b = baseCount, s = b + x)) {
CounterCell a; long v; int m;
boolean uncontended = true;
if (as == null || (m = as.length - 1) < 0 ||
(a = as[ThreadLocalRandom.getProbe() & m]) == null ||
!(uncontended =
U.compareAndSwapLong(a, CELLVALUE, v = a.value, v + x))) {
fullAddCount(x, uncontended);
return;
}
if (check <= 1)
return;
s = sumCount();
}
if (check >= 0) {
Node<K,V>[] tab, nt; int n, sc;
while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
(n = tab.length) < MAXIMUM_CAPACITY) {
int rs = resizeStamp(n);
if (sc < 0) {
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || (nt = nextTable) == null ||
transferIndex <= 0)
break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1))
transfer(tab, nt);
}
else if (U.compareAndSwapInt(this, SIZECTL, sc,
(rs << RESIZE_STAMP_SHIFT) + 2))
transfer(tab, null);
s = sumCount();
}
}
}
5、get操作
get操作中没有使用到同步的操作,所以相对来说比较简单一点。通过key的hashCode计算获得相应的位置,然后在遍历该位置上的元素,找到需要的元素,然后返回,如果没有则返回null:
/**
* Returns the value to which the specified key is mapped,
* or {@code null} if this map contains no mapping for the key.
*
* <p>More formally, if this map contains a mapping from a key
* {@code k} to a value {@code v} such that {@code key.equals(k)},
* then this method returns {@code v}; otherwise it returns
* {@code null}. (There can be at most one such mapping.)
*
* @throws NullPointerException if the specified key is null
*/
public V get(Object key) {
Node<K,V>[] tab; Node<K,V> e, p; int n, eh; K ek;
int h = spread(key.hashCode());
if ((tab = table) != null && (n = ta