今天重点是将鄙人认为JUC最复杂的代码ConrruentHashMap的实现源码(⊙o⊙)…
相对于线程安全有问题的一些常用数据类型,jdk提供了其线程安全的对应类,位于java.util.concurrent.atomic包里一共有12个类,四种原子更新方式,分别是原子更新基本类型,原子更新数组,原子更新引用和原子更新字段。Atomic包里的类基本都是使用Unsafe实现的包装类。
说起atomic包,先说下unsafe类。Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。其用法几乎充斥着AQS整个框架(park,cas)。但由于Unsafe类使Java语言拥有了类似C语言指针一样操作内存空间的能力,这无疑也增加了程序发生相关指针问题的风险。在程序中过度、不正确使用Unsafe类会使得程序出错的概率变大,使得Java这种安全的语言变得不再“安全”,所以整个类叫 Unsafe。一般用不到,用到了不一般。
如何获取:通常通过反射获取,因为java的双亲委派模式,反射显得比较简单(因为其是由BootstrapClassLoader加载)。
public static Unsafe reflectGetUnsafe() {
try {
Field field =Unsafe.class.getDeclaredField("theUnsafe");
field.setAccessible(true);
return (Unsafe) field.get(null);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
Unsafe功能介绍:Unsafe提供的API大致可分为内存操作、CAS、Class相关、对象操作、线程调度、系统信息获取、内存屏障、数组操作等几类,下面将对其相关方法和应用场景进行详细介绍。
1、内存操作:主要包含堆外内存的分配、拷贝、释放、给定地址值操作等方法。
我们在Java中创建的对象都处于堆内内存(heap)中,堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。与之相对的是堆外内存,存在于JVM管控之外的内存区域,Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法。
应用:DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。下图为DirectByteBuffer构造函数,创建DirectByteBuffer的时候,通过Unsafe.allocateMemory分配内存、Unsafe.setMemory进行内存初始化,而后构建Cleaner对象用于跟踪DirectByteBuffer对象的垃圾回收,以实现当DirectByteBuffer被垃
圾回收时,分配的堆外内存一起被释放。
2、CAS相关:
cas操作用到的地方太多了。讲一个一会要说的AtomicInteger类。通过valueOffset可以在内存中找到这个值在内存中的位置,然后用cas就可实现对其原子更新。
线程调度:括线程挂起、恢复、锁机制等方法。
//取消阻塞线程
public native void unpark(Object thread);
//阻塞线程
public native void park(boolean isAbsolute, long time);
//获得对象锁(可重入锁)
@Deprecated
public native void monitorEnter(Object o);
//释放对象锁
@Deprecated
public native void monitorExit(Object o);
//尝试获取对象锁
@Deprecated
public native boolean tryMonitorEnter(Object o);
典型应用:AQS以及JUC里面几乎到处都是。
内存屏障:避免代码重排
//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏
障后,屏障后的load操作不能被重排序到屏障前
public native void loadFence();
//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,
屏障后的store操作不能被重排序到屏障前
public native void storeFence();
//内存屏障,禁止load、store操作重排序
public native void fullFence();
应用:在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于
StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时,需要遵从如下图用例中使用的模式来确保数据的一致性。
如上图用例所示计算坐标点Point对象,包含点移动方法move及计算此点到原点的距离的方法distanceFromOrigin。在方法distanceFromOrigin中,首先,通过tryOptimisticRead方法获取乐观读标记;然后从主内存中加载点的坐标值 (x,y);而后通过StampedLock的validate方法校验锁状态,判断坐标点(x,y)从主内存加载到线程工作内存过程中,主内存的值是否已被其他线程通过move方法修改,如果validate返回值为true,证明(x, y)的值未被修改,可参与后续计算;否则,需加悲观读锁,再次从主内存加载(x,y)的最新值,然后再进行距离计算。其中,校验锁状态这步操作至关重要,需要判断锁状态是否发生改变,从而判断之前copy到线程工作内存中的值是否与主内存的值存在不一致。下图为StampedLock.validate方法的源码实现,通过锁标记与相关常量进行位运算、比较来校验锁状态,在校验逻辑之前,会通过Unsafe的loadFence方法加入一个load内存屏障,目的是避免上图用例中步骤②和StampedLock.validate中锁状态校验运算发生重排序导致锁状态校验不准确的问题。
其他的不仅用的不多,看到的都不多,就不一一列举了。
现在再看下atomic包:
基本数据包装类:AtomicInteger、AtomicLong、AtomicBoolean;
引用类型包装类:AtomicReference、AtomicStampedRerence(ABA)、AtomicMarkableReference;
数组类型包装类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
对象属性原子修改器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
其基本都是对于Unsafe类的封装。就不一一列举了。以AtomicInteger为例:
value:volatile修饰的变量。cas必须配合vl修饰
unsafe:获取unsafe类
valueOffset:可以想象一下,value必定是存于内存的某个未知。比如内存的存储位置是0-99.如何找到这个value在内存中的位置,这个valueOffset就是记录这个value的位置的。在静态代码块里初始化value偏移量。
// 设置新值(因为这里只是写所以顺序写就行)
public final void lazySet(int newValue) {
unsafe.putOrderedInt(this, valueOffset, newValue);
}
// 设置新值返回旧值
public final int getAndSet(int newValue) {
return unsafe.getAndSetInt(this, valueOffset, newValue);
}
// CAS修改值
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// CAS修改值
public final boolean weakCompareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 返回原值,value加1(先返回再加1)
public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}
// 返回原值,value减1(先返回再减1)
public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}
// 返回原值,value加delta
public final int getAndAdd(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta);
}
// 返回加1后的值
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
// 返回减1后的值
public final int decrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
}
// 返回value加delta的值
public final int addAndGet(int delta) {
return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}
实际上这里都是调用的:getAndAddInt
看下这个方法里的代码:非常简单,返回修改前的值。
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
//获取对象中offset偏移地址对应的整型field的值
var5 = this.getIntVolatile(var1, var2);
//cas
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
重点说两个类,线程安全的 ConcurrentHashMap 和 CopyOnWriteArrayList (SynchronizedMap 和 SynchronizedList根据场景不同使用,在插入操作远远超过读取时,synchronize可以避免大量的内存压力)
JDK1.7 的 实 现 上 , ConrruentHashMap 由一个个 Segment 组 成 , 简 单 来 说 ,ConcurrentHashMap 是一个 Segment 数组,它通过继承 ReentrantLock 来进行加锁,通过每次锁住一个 segment 来保证每个 segment 内的操作的线程安全性从而实现全局线程安全。Segment默认16.也就是支持16个线程的并发操作,创建后Segment数量不能修改,扩容扩的是Segment 大小。缺点嘛:分段内存碎屏化,当segment变大时,锁粒度显得比较大了。结构如图:在原来的HashMap的基础上增加了segment概念,使原来的链表+数组又多了一层segment概念。
1.8对ConrruentHashMap的改进:
1、结构的改进:由原来的数组+链表改成了 数组+链表->红黑树。主要是应为当hash碰撞明显的时候,单条链路的长度过长,那么查询某个节点的时间复杂度就变为 O(n);因此对于队列长度超过 8 的列表,采用了红黑树的结构,查询的时间复杂度就会降低到O(logN),在数节点小于6的时候又会恢复到链表结构。
2、取消了 segment 分段设计,直接使用 Node 数组来保存数据,并且采用 Node 数组元素作为锁来实现每一行数据进行加锁来进一步减少并发冲突的概率。
ConrruentHashMap与HashMap的结构是一致的,但是为了保证线程安全性,ConcurrentHashMap 的实现会"稍微"复杂一些。。。接下来重点讲的是1.8的put操作。由于接口非常的复杂,先说下它主要干啥:
1.put时先初始化table。默认大小16个桶。
2.table中桶的数量大于16*4,并且此桶的链表数量在大于8时,将转为红黑树,较少到6又变链表
3.计算总数方式改变。在没有线程竞争的时候,用baseCount计数。有线程竞争的时候转为用counterCells。即每一部分桶里元素个数记录在一个counterCell中。table大小就是所有cell+baseCount的数目。目的:分段治理,减少线程激烈的自旋cas导致的cpu空跑。
4.达到扩容阀值(默认0.75因子),也是一个分段治理思想扩容:先根据一定规则把table分成不同的bound区间,每个bound区间里面的桶的转移由各个线程单独负责,然后最后检查下转移情况。
5.转移采用原位置转移或者原位置+原table长度的头插法方式。一次计算hash即可。
6.功能设计强大,让人叹为观止。缺点:代码复杂难懂
7.get方法咱就不列了,其get方法相对来讲也是比较复杂的。不过get方式无锁操作,最后的取值操作实际是根据元素在内存中的位置offset直接取的(volatile),保证可见性。不加锁不自旋。
//put 接口,直接调用putVal
public V put(K key, V value) {
return putVal(key, value, false);
}
/** Implementation for put and putIfAbsent */
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
//根据put值计算hashCode
int hash = spread(key.hashCode());
//记录链表的长度
int binCount = 0;
//自旋
for (Node[] tab = table;;) {
Node f; int n, i, fh;
// 如果table是空,则先进行initTable初始化
if (tab == null || (n = tab.length) == 0)
//初始化方法见下面
tab = initTable();
//如果已经被初始化,通过hash值对应的数组下标得到第一个Node节点; tabAt是以getObjectVolatile获取最新值
/**
*这里提示一下:数组volatile修饰只保证其引用可见,要保证其中元素可见,必须要像getObjectVolatile中以其元素在内存中的偏移量去取
**/
//如果该下标返回的节点为空,(无hash碰撞,第一次放入)
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//则直接通过 cas 将新的值封装成Node插入即可;如果失败,说明此数组下标节点被别的线程添加了,则进入下一次循环
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
//这里的MOVED值是-1原注释是:hash for forwarding nodes
//表示如果其hash是-1表示当前要转移的节点,也就是正在扩容转移,则当前线程则帮助他进行扩容。
else if ((fh = f.hash) == MOVED)
//帮助扩容接口,在下面
tab = helpTransfer(tab, f);
else {
//进入到这个分支,说明f是当前nodes数组对应hash位置节点的头节点,并且不为空,这个时候要看情况是加到链表后面还是转红黑树
V oldVal = null;
//给对应的头结点加锁.这里直接用了隐式锁,猜想是为了减少复杂度,毕竟现在的设计以node为锁节点粒度已经很细了
synchronized (f) {
//锁内的方法就比较好理解了
//加锁后,再次判断对应下标位置是否为 f 节点
if (tabAt(tab, i) == f) {
//头结点的hash值大于 0,说明是链表。!!!!是链表则第二部binCount就不会等于0
if (fh >= 0) {
//用来记录链表的长度
binCount = 1;
//遍历链表
for (Node e = f;; ++binCount) {
K ek;
//如果发现相同的 key
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
//则判断是否需要进行值的覆盖,默认直接覆盖(put方法可以指定)
if (!onlyIfAbsent)
e.val = value;
break;
}
//一直遍历到链表的最末端,直接把新的值加入到链表的最后面
Node pred = e;
if ((e = e.next) == null) {
pred.next = new Node(hash, key,
value, null);
break;
}
}
}
//若果当前node是一颗红黑树
else if (f instanceof TreeBin) {
Node p;
binCount = 2;
//则调用红黑树的插入方法,同样相同则覆盖
if ((p = ((TreeBin)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
//不等于0。。。证明上面是进行的链表操作
if (binCount != 0) {
//链表长度大于等于转化红黑树 长度0 static final int TREEIFY_THRESHOLD = 8;
if (binCount >= TREEIFY_THRESHOLD)
//把链表转换为树结构
treeifyBin(tab, i);
//这里可以看出 ,如果是key相同覆盖的话会返回覆盖之前的值哦。
if (oldVal != null)
return oldVal;
break;
}
}
}
//增加ConcurrentHashMap中的元素个数,这个时候还会触发扩容操作。
addCount(1L, binCount);
return null;
}
/**
* Initializes table, using the size recorded in sizeCtl.
*初始化数组
*重要标识参数sizeCtl
*-1:代表正在初始化
* 0:表示Node数组还没有被初始化,
* 大于0:代表下一次扩容要达到的元素的个数
*-N:代表有 N-1 有二个线程正在进行扩容操作
*/
private final Node[] initTable() {
Node[] tab; int sc;
while ((tab = table) == null || tab.length == 0) {
//这里小于0证明其现在已经被其他线程抢占了初始化步骤,让出cpu时间片
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
//cas将sizeCtl替换为-1,表示当前线程取得了初始化的资格
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
if ((tab = table) == null || tab.length == 0) {
//这里可以看到,初始化的默认容量大小 private static final int DEFAULT_CAPACITY = 16;
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
//根据容量构建初始的Node数组
@SuppressWarnings("unchecked")
Node[] nt = (Node[])new Node,?>[n];
table = tab = nt;
//扩容因子0.75
sc = n - (n >>> 2);
}
} finally {
//将扩容临界值赋值sizeCt。如果是默认的,则是16*0.75 =12
sizeCtl = sc;
}
break;
}
}
return tab;
}
/**
* Replaces all linked nodes in bin at given index unless table is
* too small, in which case resizes instead.
*链表转树结构。这里有个最小树型化的阈值判断
*/
private final void treeifyBin(Node[] tab, int index) {
Node b; int n, sc;
if (tab != null) {
//并不是一达到8就将链表转红黑树的,MIN_TREEIFY_CAPACITY:哈希表中的容量 > 该值时,才允许树形化链表。否则直接扩容
//为了避免进行扩容、树形化选择的冲突,这个值不能小于 4 * TREEIFY_THRESHOLD() 也就是不小于32即可
/** 原注释
* The smallest table capacity for which bins may be treeified.
* (Otherwise the table is resized if too many nodes in a bin.)
* The value should be at least 4 * TREEIFY_THRESHOLD to avoid
* conflicts between resizing and treeification thresholds.
*static final int MIN_TREEIFY_CAPACITY = 64;
*/
if ((n = tab.length) < MIN_TREEIFY_CAPACITY)
tryPresize(n << 1);
else if ((b = tabAt(tab, index)) != null && b.hash >= 0) {
//将链表树行化
synchronized (b) {
if (tabAt(tab, index) == b) {
TreeNode hd = null, tl = null;
for (Node e = b; e != null; e = e.next) {
TreeNode p =
new TreeNode(e.hash, e.key, e.val,
null, null);
if ((p.prev = tl) == null)
hd = p;
else
tl.next = p;
tl = p;
}
setTabAt(tab, index, new TreeBin(hd));
}
}
}
}
}
/**
* 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.
* x 表示这次需要在表中增加的元素个数,check 参数表示是否需要进行扩容检查。这里可以看出,大于等于0。都需要进行检查要不要扩容
* @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;
//判断 counterCells 是否为空,如果为空,就通过 cas 操作尝试修改 baseCount 变量,
//对这个变量进行原子累加操作(做这个操作的意义是:如果在没有竞争的情况下,仍然采用 baseCount 来记录元素个数)
//如果 cas 失败说明存在竞争,这个时候不能再采用 baseCount 来累加,而是通过CounterCell 来记录
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();
}
//下面的方法就类似于 helpTransfer 里面的逻辑了
if (check >= 0) {
Node[] 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();
}
}
}
/**帮助扩容接口
* Helps transfer if a resize is in progress.
*/
final Node[] helpTransfer(Node[] tab, Node f) {
Node[] nextTab; int sc;
//判断下此时是否扩容结束了,nextTab=null的时候说明扩容已经结束了
if (tab != null && (f instanceof ForwardingNode) && (nextTab = ((ForwardingNode)f).nextTable) != null) {
//这里resizeStamp 用来生成一个和扩容有关的扩容戳。
/**
* Returns the stamp bits for resizing a table of size n.
* Must be negative when shifted left by RESIZE_STAMP_SHIFT.
* resizeStamp Integer.numberOfLeadingZeros(n) | (1 << (RESIZE_STAMP_BITS - 1))
* Integer.numberOfLeadingZeros(n):方法说的通俗一点就是 tab.length转二进制最高位的1之前补码0的个数。如果n是16,二进制就是10000,二进制为32位,其补码前面有27个0,则函数返回27.
* 然后27与然后与(1 << (RESIZE_STAMP_BITS - 1)) | 其结果就是: 0000 0000 0000 0000 1000 0000 0001 1100
* 数组长度不变,rs则不会变化。则表示还为扩容完成
*/
int rs = resizeStamp(tab.length);
//再次判断是否扩容完事
while (nextTab == nextTable && table == tab &&
(sc = sizeCtl) < 0) {
//1.(sc >>> RESIZE_STAMP_SHIFT) != rs说明当前线程不在同一次扩容中,sc右移16位的结果理论上应该和rs相同,但如果不同,说明此时的数组长度已经变了,可能是当前线程还在上一次扩容中
//2.sc == rs + 1说明有最后一个线程在做最后收尾工作
//3.sc == rs + MAX_RESIZERS。。就是不能超过规定的最大线程
//4.transferIndex <= 0说明要扩容的区间已经都分配完了
if ((sc >>> RESIZE_STAMP_SHIFT) != rs || sc == rs + 1 ||
sc == rs + MAX_RESIZERS || transferIndex <= 0)
break;
//这里将sizeCtl的值自增1,表明参与扩容的线程数量+1
if (U.compareAndSwapInt(this, SIZECTL, sc, sc + 1)) {
transfer(tab, nextTab);
break;
}
}
return nextTab;
}
return table;
}
/**
* 扩容接口:最复杂的接口,晦涩难懂,连蒙带猜
* 把这个table分成几个bound区间每个线程负责迁移本bound区间所有迁移工作,才会
* 如果迁移做完,最后一个线程会再次检查一下
* 当然了,如果只有一个线程,它就会完成全部的迁移工作
*/
private final void transfer(Node[] tab, Node[] nextTab) {
int n = tab.length, stride;
//定义bound区间的长度单位:stride 根据数组长度,cpu可用线程数
if ((stride = (NCPU > 1) ? (n >>> 3) / NCPU : n) < MIN_TRANSFER_STRIDE)
stride = MIN_TRANSFER_STRIDE;
//如果nextTab是null,当前线程是第一个进来的扩容线程
if (nextTab == null) {
try {
//创建一个2倍于旧容量的Node数组赋值给nextTab,旧的数据会迁过来
@SuppressWarnings("unchecked")
Node[] nt = (Node[]) new Node, ?>[n << 1];
nextTab = nt;
} catch (Throwable ex) {
//异常停止扩容,上面只做了创建新数组操作,基本可以认为是OOM
sizeCtl = Integer.MAX_VALUE;
return;
}
nextTable = nextTab;
//transferIndex指针初始化为旧数组容量
transferIndex = n;
}
int nextn = nextTab.length;
//创建一个ForwardingNode节点,将nextTab赋值给它,此时还是一个空数组
ForwardingNode fwd = new ForwardingNode(nextTab);
//advance表示是否完成了当前桶的迁移工作
boolean advance = true;
//finishing表示是否完成了所有的迁移工作(这里就是检查用的)
boolean finishing = false;
//i指向当前桶的位置,bound指向当前线程被分配的扩容区间边界点
for (int i = 0, bound = 0; ; ) {
Node f;
int fh;
//分配bound区间,更新当前桶位置i
while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing)
advance = false;
else if ((nextIndex = transferIndex) <= 0) {
//小于等于0说明bound区间都被分配了
i = -1;
advance = false;
} else if (U.compareAndSwapInt
(this, TRANSFERINDEX, nextIndex,
nextBound = (nextIndex > stride ?
nextIndex - stride : 0))) {
bound = nextBound;
i = nextIndex - 1;
advance = false;
}
}
if (i < 0 || i >= n || i + n >= nextn) {
int sc;
//迁移工作都做完
if (finishing) {
nextTable = null;
//table此时指向两倍容量,扩容后的数组
table = nextTab;
/**
*设置新的sizeCtl阈值
**/
sizeCtl = (n << 1) - (n >>> 1);
return;
}
/*
*走到这里说明:还有走最后一次检查
*/
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, sc - 1)) {
if ((sc - 2) != resizeStamp(n) << RESIZE_STAMP_SHIFT)
return;
finishing = advance = true;
i = n;
}
} else if ((f = tabAt(tab, i)) == null)
/*
*如果旧数组上该桶为null,也就是说该桶上没有数据,ForwardingNode节点上的hash
*值设置成MOVED,这样其他线程就不会再去操作它
*/
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED)
advance = true;
else {
//锁住这个桶
synchronized (f) {
if (tabAt(tab, i) == f) {
Node ln, hn;
if (fh >= 0) {
/**
* 这里的逻辑其实就是将原桶上这个链表上每个节点hash值在数组容量二进制数为1的那个位置处去按位与判断是0还是1,以此来拆分出两个链表。
* 然后根据结果如果为0的话最后就会插入到新数组的原位置,为1就插入到原位置+旧数组容量的位置。但是在ConcurrentHashMap中做了进一步的
* 优化::如果链表上所有节点计算出来的值都是0的话,其实这个时候我可以不用去遍历,直接将链表的头节点直接插入到新数组的原位置处就可以了
*/
int runBit = fh & n;
Node lastRun = f;
for (Node p = f.next; p != null; p = p.next) {
int b = p.hash & n;
if (b != runBit) {
runBit = b;
lastRun = p;
}
}
if (runBit == 0) {
ln = lastRun;
hn = null;
} else {
hn = lastRun;
ln = null;
}
for (Node p = f; p != lastRun; p = p.next) {
int ph = p.hash;
K pk = p.key;
V pv = p.val;
if ((ph & n) == 0)
ln = new Node(ph, pk, pv, ln);
else
hn = new Node(ph, pk, pv, hn);
}
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
} else if (f instanceof TreeBin) {
//红黑树的迁移逻辑
TreeBin t = (TreeBin) f;
TreeNode lo = null, loTail = null;
TreeNode hi = null, hiTail = null;
int lc = 0, hc = 0;
for (Node e = t.first; e != null; e = e.next) {
int h = e.hash;
TreeNode p = new TreeNode
(h, e.key, e.val, null, null);
if ((h & n) == 0) {
if ((p.prev = loTail) == null)
lo = p;
else
loTail.next = p;
loTail = p;
++lc;
} else {
if ((p.prev = hiTail) == null)
hi = p;
else
hiTail.next = p;
hiTail = p;
++hc;
}
}
ln = (lc <= UNTREEIFY_THRESHOLD) ? untreeify(lo) :
(hc != 0) ? new TreeBin(lo) : t;
hn = (hc <= UNTREEIFY_THRESHOLD) ? untreeify(hi) :
(lc != 0) ? new TreeBin(hi) : t;
setTabAt(nextTab, i, ln);
setTabAt(nextTab, i + n, hn);
setTabAt(tab, i, fwd);
advance = true;
}
}
}
}
}
}
CopyOnWriteArrayList:(add和get接口)
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
//很简单,没啥说的。。
public boolean add(E e) {
//获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
//复制到新的数组,然后在后面追加元素
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
//get方法,没啥说的。。。更简单。但是注意获取到的不一定是最新的。。
/**
* Gets the array. Non-private so as to also be accessible
* from CopyOnWriteArraySet class.
*/
final Object[] getArray() {
return array;
}