一、:LockSupport
①LockSupport简介
在AQS、ReentrantLock、ReentrantReadWriteLock和Condition中,我们经常能看到LockSupport.park()方法和LockSupport.unpark()方法的调用。
LockSupprot是线程的阻塞原语,用来阻塞和唤醒线程。每个使用LockSupport的线程都会与一个许可(permit)关联,如果该许可可用,并且可在线程中使用,调用park()将会立即返回,否则可能阻塞。如果许可尚不可用,可以调用unpark()使其可用。但是注意许可不可重入,也就是说只能调用一次park()方法,否则会一直阻塞。
②LockSupport中的方法
所有方法,根据作用不同,大体可以分为两类:阻塞和唤醒。
1、阻塞方法
●void park():阻塞当前线程,如果调用unpark方法或者当前线程被中断,从能从park()方法中返回。
●void park(Object blocker):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。
●void parkNanos(long nanos):阻塞当前线程,最长不超过nanos纳秒,增加了超时返回的特性。
●void parkNanos(Object blocker, long nanos):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。
●void parkUntil(long deadline):阻塞当前线程,知道deadline时间。
●void parkUntil(Object blocker, long deadline):功能同上,入参增加一个Object对象,用来记录导致线程阻塞的阻塞对象,方便进行问题排查。
2、唤醒方法
●void unpark(Thread thread):唤醒处于阻塞状态的指定线程。
3、细节分析
每个线程都有一个许可(permit),permit只有两个值1和0(默认是0-不可用)。
●当调用unpark()方法,就会将thread线程的许可permit设置成1(注意多次调用unpark方法,不会累加,permit值还是1)。
●当调用park()方法,如果当前线程的permit是1,那么将permit设置为0,并立即返回。如果当前线程的permit是0,那么当前线程就会阻塞,直到别的线程将当前线程的permit设置为1,park方法会将permit再次设置为0,并返回。
注意:
●因为permit默认是0,所以一开始调用park()方法,线程必定会被阻塞。调用unpark(thread)方法后,会自动唤醒thread线程,即park方法立即返回。
●park系列的方法就是直接阻塞当前线程的,所以可以不需要线程变量参数。而unpark()方法是唤醒对应线程的,所以必须传递线程变量thread。
synchronzed致使线程阻塞,线程会进入到BLOCKED状态,而调用LockSupprt方法阻塞线程会致使线程进入到WAITING状态。
对于WAITING等待状态有两种唤醒方式:
●调用对应的唤醒方法。这里就是LockSupport的unpark()方法。
●调用该线程变量的interrupt()方法,会唤醒该线程,并抛出InterruptedException异常。
二、:CountDownLatch
①CountDownLatch简介
在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join方法,让主线程等待被join的线程执行完之后,主线程才能继续往下执行。当然,使用线程间消息通信机制也可以完成。其实,java并发工具类中为我们提供了类似“倒计时”这样的工具类,可以十分方便的完成所说的这种业务场景,这就是CountDownLatch。
②CountDownLatch中的方法
1、构造方法
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
}
根据构造方法及其中的Sync的声明可以看出:
●CountDownLatch构造器中的count最终还是传递了ASQ中的state。
●CountDownLatch是采用共享锁来实现的。
2、await()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果计数器值不等于0,则会调用doAcquireSharedInterruptibly()方法
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
方法作用主要是使当前线程在锁存器倒计数到达0之前一直等待,除非线程被中断。根据其中的判断条件不难看出,如果计数器值不等于0,则会调用doAcquireSharedInterruptibly()方法。
ⅰ、doAcquireSharedInterruptibly()
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
// 根据前面的方法可以看出,如果计数器值不等于0,那么r会一直小于0
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
方法作用主要是自旋尝试获取同步状态(直至计数器值为0或被中断)。
3、countDown()方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
方法作用主要是递减锁存器的计数。如果计数到达0,则释放所有等待的线程。其中的doReleaseShared()是使用的AQS中的方法。同时,我们可以看出,当调用countDown()方法后,当前线程是不会被阻塞的,会继续往下执行。
三、:CyclicBarrier
①CyclicBarrier简介
CyclicBarrier也是一种多线程并发控制的实用工具,和CountDownLatch一样具有等待计数的功能,但是相比于CountDownLatch功能更加强大。它们之间最大的区别在于CyclicBarrier的计数器可以重置,相当于可以循环使用。
②CyclicBarrier中的方法
1、构造方法
CyclicBarrier的内部是使用重入锁ReentrantLock和Condition。它有两个构造函数:
●CyclicBarrier(int parties):创建一个新的yiCyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动barrier时执行预定义的操作。
●CyclicBarrier(int parties, Runnable barrierAction) :创建一个新的CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动barrier时执行给定的屏障操作,该操作由最后一个进入barrier的线程执行。
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
// parties表示拦截线程的数量
this.parties = parties;
this.count = parties;
// barrierAction为CyclicBarrier接收的Runnable命令,用于在线程到达屏障时,优先执行barrierAction,用于处理更加复杂的业务场景
this.barrierCommand = barrierAction;
}
2、await()方法
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
await()方法作用类比CountDownLatch,是在所有参与者都已经在此barrier上调用await()方法之前,将一直等待。其核心实现在dowait()方法上。
ⅰ、dowait()
private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
// 获取锁
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 分代
final Generation g = generation;
// 当前generation“已损坏”,抛出BrokenBarrierException异常(抛出该异常一般都是某个线程在等待某个处于“断开”状态的CyclicBarrie)
if (g.broken)
throw new BrokenBarrierException();
// 如果线程中断,终止CyclicBarrier
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// 进来一个线程,将count减1
int index = --count;
// 如果count为0,表示所有线程均已到位,触发Runnable任务
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 触发任务
if (command != null)
command.run();
ranAction = true;
// 唤醒所有等待线程,并更新generation
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// loop until tripped, broken, interrupted, or timed out
for (;;) {
try {
// 如果不是超时等待,则调用Condition.await()方法等待
if (!timed)
trip.await();
else if (nanos > 0L)
// 如果是超时等待,调用Condition.awaitNanos()方法等待
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
// We're about to finish waiting even if we had not
// been interrupted, so this interrupt is deemed to
// "belong" to subsequent execution.
Thread.currentThread().interrupt();
}
}
if (g.broken)
throw new BrokenBarrierException();
// generation已经更新,返回index
if (g != generation)
return index;
// 如果是超时等待,并且时间已到,终止CyclicBarrier,并抛出异常
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
// 释放锁
lock.unlock();
}
}
代码整体长度比较长,但是做的事情比较单纯,就是如果该线程不是到达的最后一个线程,则该线程会一直处于等待状态。其中会有一些跳出的情况:
●最后一个线程到达。(即index == 0)
●超出了指定时间。(超时等待的情况)
●其他的某个线程中断了当前线程。
●其他的某个线程中断了另一个等待的线程。
●其他的某个线程在等待barrier时超时。(超时等待的情况)
●其他的某个线程在此barrier调用reset()方法。(reset()方法用于将屏障重置为初始状态)
3、breakBarrier()方法
代码中,Generation描述着CyclicBarrier的更显换代。在CyclicBarrier中,同一批线程属于同一代。当有parties个线程到达barrier,generation就会被更新换代。其中broken标识该当前CyclicBarrier是否已经处于中断状态。默认barrier是没有损坏的。
private static class Generation {
boolean broken = false;
}
当barrier损坏了或者有一个线程中断了,则通过breakBarrier()来终止所有的线程:
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
在breakBarrier()中除了将broken设置为true,还会调用signalAll将在CyclicBarrier处于等待状态的线程全部唤醒。
4、nextGeneration()方法
当所有线程都已经到达barrier处(index == 0),则会通过nextGeneration()进行更新换代操作,在这个步骤中,做了三件事:唤醒所有线程、重置count、更新generation。
private void nextGeneration() {
// signal completion of last generation
trip.signalAll();
// set up next generation
count = parties;
generation = new Generation();
}
5、reset()方法
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
breakBarrier(); // break the current generation
nextGeneration(); // start a new generation
} finally {
lock.unlock();
}
}
根据前面的说明,可以看到此方法主要作用是将屏障重置为初始状态。如果当前有线程正在临界点等待的话,将抛出BrokenBarrierException。
③CountDownLatch VS CyclicBarrier
通过上面的分析,可以得出CountDownLatch和CyclicBarrier的不同侧重点:
●CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行。
CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。
●调用CountDownLatch的countDown()方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await()方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行。
●CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能。
●CountDownLatch是不能复用的,而CyclicLatch是可以复用的。
四、:Semaphore
①Semaphore简介
Semaphore可以理解为信号量,用于控制资源能够被并发访问的线程数量,以保证多个线程能够合理的使用特定资源。Semaphore就相当于一个许可证,线程需要先通过acquire()方法获取该许可证,该线程才能继续往下执行,否则只能在该方法处阻塞等待。当执行完业务功能后,需要通过release()方法将许可证归还,以便其他线程能够获得许可证继续执行。
Semaphore可以用于做流量控制,特别是公共资源有限的应用场景,比如数据库连接。假如有多个线程读取数据后,需要将数据保存在数据库中,而可用的最大数据库连接只有10个,这时候就需要使用Semaphore来控制能够并发访问到数据库连接资源的线程个数最多只有10个。在限制资源使用的应用场景下,Semaphore是特别合适的。
②Semaphore中的方法
1、构造方法
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
可以看到,在Semaphore的构造方法中还支持指定是否具有公平性,默认的是非公平,这样也是为了保证吞吐量。
同时,特殊情况下,当信号量Semaphore = 1时,它可以当作互斥锁使用。其中0、1就相当于它的状态,当=1时表示其他线程可以获取;当=0时,排他,即其他线程必须要等待。
2、acquire()方法
public void acquire() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
获取一个许可,默认使用的是可中断方式,如果尝试获取许可失败,会进入AQS的队列中排队。
这里的tryAcquireShared()方法实现根据公平与非公平模式而不同。如果我们选择非公平模式,则调用NonfairSync的tryAcquireShared()方法,否则调用FairSync的tryAcquireShared()方法。
doAcquireSharedInterruptibly()则是沿用AQS中的方法。
ⅰ、NonfairSync的tryAcquireShared()
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
// 获取当前的信号量许可
int available = getState();
// 计算剩余的信号量许可数
int remaining = available - acquires;
// CAS设置信号量
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
ⅱ、FairSync的tryAcquireShared()
protected int tryAcquireShared(int acquires) {
for (;;) {
// 判断该线程是否位于同步队列的头
if (hasQueuedPredecessors())
return -1;
// 获取当前的信号量许可
int available = getState();
// 计算剩余的信号量许可数
int remaining = available - acquires;
// CAS设置信号量
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
3、release()方法
public void release() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int releases) {
for (;;) {
// 获取当前的信号量许可
int current = getState();
// 信号量的许可数计算(当前信号许可数 + 待释放的信号许可数)
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
// 设置可获取的信号许可数为next
if (compareAndSetState(current, next))
return true;
}
}
释放一个许可,释放一个许可时state的值会加1,并且会唤醒下一个等待获取许可的线程。
五、:Exchanger
①Exchanger简介
Exchanger是一个用于线程间协作的工具类,使得两个线程间能够交换数据。它提供了一个交换的同步点,在这个同步点两个线程能够交换数据。具体交换数据是通过exchange()方法来实现的,如果一个线程先执行exchange()方法,那么它会同步等待另一个线程也执行exchange()方法,这个时候两个线程就都达到了同步点,两个线程就可以交换数据。
②Exchanger中的方法
Exchanger是几个工具类中最简单的也是最复杂的,简单在于API非常简单,只有一个构造方法和两个exchange()方法,最复杂在于它的实现是最复杂的。
在Exchanger中,有几个重要的成员变量:
// 为每个线程保留唯一的一个Node节点
private final Participant participant;
// 数组槽
private volatile Node[] arena;
// 单个槽
private volatile Node slot;
1、构造方法
public Exchanger() {
participant = new Participant();
}
static final class Participant extends ThreadLocal<Node> {
public Node initialValue() { return new Node(); }
}
@sun.misc.Contended static final class Node {
// arena的下标
int index; // Arena index
// 上一次记录的Exchanger.bound
int bound; // Last recorded value of Exchanger.bound
// 在当前bound下CAS失败的次数
int collides; // Number of CAS failures at current bound
// 伪随机数,用于自旋
int hash; // Pseudo-random for spins
// 这个线程的当前项,也就是需要交换的数据
Object item; // This thread's current item
// 做releasing操作的线程传递的项
volatile Object match; // Item provided by releasing thread
// 挂起时设置线程值,其他情况下为null
volatile Thread parked; // Set to this thread when parked, else null
}
2、exchange()方法
当一个线程执行该方法的时候,会等待另一个线程也执行该方法,因此两个线程就都达到了同步点。
// 将数据交换给另一个线程,同时返回获取的数据
public V exchange(V x) throws InterruptedException {}
// 与上一个方法功能基本一样,只不过这个方法同步等待的时候,增加了超时时间
public V exchange(V x, long timeout, TimeUnit unit) throws InterruptedException, TimeoutException {}
exchange()方法有两个,第二个可以看作是前一个的功能升级。这里我们以主要功能,也就是第一个方法继续跟踪代码。
@SuppressWarnings("unchecked")
public V exchange(V x) throws InterruptedException {
Object v;
Object item = (x == null) ? NULL_ITEM : x; // translate null args
if ((arena != null ||
(v = slotExchange(item, false, 0L)) == null) &&
((Thread.interrupted() || // disambiguates null return
(v = arenaExchange(item, false, 0L)) == null)))
throw new InterruptedException();
return (v == NULL_ITEM) ? null : (V)v;
}
这个方法主要逻辑在if判断处:arena为数组槽,如果为null,则执行slotExchange()方法,否则判断线程是否中断,如果中断值抛出InterruptedException异常,没有中断则执行arenaExchange()方法。如果slotExchange()方法执行失败了就执行arenaExchange()方法,最后返回结果V。
NULL_ITEM 为一个空节点,其实就是一个Object对象而已。
ⅰ、slotExchange()
private final Object slotExchange(Object item, boolean timed, long ns) {
// 获取当前线程的节点
Node p = participant.get();
// 获取当前线程
Thread t = Thread.currentThread();
// 如果线程中断,直接返回
if (t.isInterrupted()) // preserve interrupt status so caller can recheck
return null;
// 自旋
for (Node q;;) {
if ((q = slot) != null) {
// 尝试CAS替换
if (U.compareAndSwapObject(this, SLOT, q, null)) {
// 当前线程的项,也就是需要交换的数据
Object v = q.item;
// 做release操作的线程传递的项
q.match = item;
// 挂起时设置线程值
Thread w = q.parked;
// 挂起线程不为null,线程挂起
if (w != null)
U.unpark(w);
return v;
}
// 如果CAS操作失败了,则创建arena
// bound是上次Exchanger.bound
// create arena on contention, but continue until slot null
if (NCPU > 1 && bound == 0 &&
U.compareAndSwapInt(this, BOUND, 0, SEQ))
arena = new Node[(FULL + 2) << ASHIFT];
}
// 如果arena不为null,直接返回,进入arenaExchange逻辑处理
else if (arena != null)
return null; // caller must reroute to arenaExchange
else {
p.item = item;
if (U.compareAndSwapObject(this, SLOT, null, p))
break;
p.item = null;
}
}
// 等待release,自旋并阻塞
// await release
int h = p.hash;
long end = timed ? System.nanoTime() + ns : 0L;
int spins = (NCPU > 1) ? SPINS : 1;
Object v;
while ((v = p.match) == null) {
if (spins > 0) {
h ^= h << 1; h ^= h >>> 3; h ^= h << 10;
if (h == 0)
h = SPINS | (int)t.getId();
else if (h < 0 && (--spins & ((SPINS >>> 1) - 1)) == 0)
Thread.yield();
}
else if (slot != p)
spins = SPINS;
else if (!t.isInterrupted() && arena == null &&
(!timed || (ns = end - System.nanoTime()) > 0L)) {
U.putObject(t, BLOCKER, this);
p.parked = t;
if (slot == p)
U.park(false, ns);
p.parked = null;
U.putObject(t, BLOCKER, null);
}
else if (U.compareAndSwapObject(this, SLOT, p, null)) {
v = timed && ns <= 0L && !t.isInterrupted() ? TIMED_OUT : null;
break;
}
}
U.putOrderedObject(p, MATCH, null);
p.item = null;
p.hash = h;
return v;
}
●程序首先通过participant获取当前线程节点Node。接着检测是否中断,如果中断则返回null,等待后续抛出InterruptedException异常。
●如果slot不为null,则进行slot消除,成功直接返回数据V,否则失败,则创建arena消除数组。
●如果slot为null,但arena不为null,则返回null,进入arenaExchange逻辑。
●如果slot为null,且arena也为null,则尝试占领该slot,失败重试,成功则跳出循环进入spin+block(自旋+阻塞)模式。
●在自旋+阻塞模式中,首先取得结束时间和自旋次数。
如果match(做release操作的线程传递的项)为null,其首先尝试spins+随机次自旋(改自旋使用当前节点中的hash,并改变之)和退让。
当自旋数为0后,如果slot发生了改变(slot != p)则重置自旋数并重试。
否则如果:当前未中断 且 arena为null 且 (当前不是限时版本或者限时版本+当前时间未结束):阻塞或者限时阻塞。
否则如果:当前中断 或者 arena不为null 或者 (当前为限时版本+时间已经结束):不限时版本:置v为null;限时版本:如果时间结束以及未中断则TIMED_OUT;否则给出null(原因是探测到arena非空或者当前线程中断)。
match不为空时跳出循环。
slot为单个槽,arena为数组槽。他们都是Node类型。在这里可能会感觉到疑惑,slot作为Exchanger交换数据的场景,应该只需要一个就可以了啊?为何还多了一个Participant和数组类型的arena呢?
一个slot交换场所原则上来说应该是可以的,但实际情况却不是如此,多个参与者使用同一个交换场所时,会存在严重伸缩性问题。既然单个交换场所存在问题,那么我们就安排多个,也就是数组arena。通过数组arena来安排不同的线程使用不同的slot来降低竞争问题,并且可以保证最终一定会成对交换数据。但是Exchanger不是一来就会生成arena数组来降低竞争,只有当产生竞争时才会生成arena数组。那么怎么将Node与当前线程绑定呢?答案就是Participant,Participant的作用就是为每个线程保留唯一的一个Node节点,它继承ThreadLocal,同时在Node节点中记录在arena中的下标index。
ⅱ、arenaExchange()
private final Object arenaExchange(Object item, boolean timed, long ns) {
Node[] a = arena;
Node p = participant.get();
for (int i = p.index;;) { // access slot at i
int b, m, c; long j; // j is raw array offset
Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
if (q != null && U.compareAndSwapObject(a, j, q, null)) {
Object v = q.item; // release
q.match = item;
Thread w = q.parked;
if (w != null)
U.unpark(w);
return v;
}
else if (i <= (m = (b = bound) & MMASK) && q == null) {
p.item = item; // offer
if (U.compareAndSwapObject(a, j, null, p)) {
long end = (timed && m == 0) ? System.nanoTime() + ns : 0L;
Thread t = Thread.currentThread(); // wait
for (int h = p.hash, spins = SPINS;;) {
Object v = p.match;
if (v != null) {
U.putOrderedObject(p, MATCH, null);
p.item = null; // clear for next use
p.hash = h;
return v;
}
else if (spins > 0) {
h ^= h << 1; h ^= h >>> 3; h ^= h << 10; // xorshift
if (h == 0) // initialize hash
h = SPINS | (int)t.getId();
else if (h < 0 && // approx 50% true
(--spins & ((SPINS >>> 1) - 1)) == 0)
Thread.yield(); // two yields per wait
}
else if (U.getObjectVolatile(a, j) != p)
spins = SPINS; // releaser hasn't set match yet
else if (!t.isInterrupted() && m == 0 &&
(!timed ||
(ns = end - System.nanoTime()) > 0L)) {
U.putObject(t, BLOCKER, this); // emulate LockSupport
p.parked = t; // minimize window
if (U.getObjectVolatile(a, j) == p)
U.park(false, ns);
p.parked = null;
U.putObject(t, BLOCKER, null);
}
else if (U.getObjectVolatile(a, j) == p &&
U.compareAndSwapObject(a, j, p, null)) {
if (m != 0) // try to shrink
U.compareAndSwapInt(this, BOUND, b, b + SEQ - 1);
p.item = null;
p.hash = h;
i = p.index >>>= 1; // descend
if (Thread.interrupted())
return null;
if (timed && m == 0 && ns <= 0L)
return TIMED_OUT;
break; // expired; restart
}
}
}
else
p.item = null; // clear offer
}
else {
if (p.bound != b) { // stale; reset
p.bound = b;
p.collides = 0;
i = (i != m || m == 0) ? m : m - 1;
}
else if ((c = p.collides) < m || m == FULL ||
!U.compareAndSwapInt(this, BOUND, b, b + SEQ + 1)) {
p.collides = c + 1;
i = (i == 0) ? m : i - 1; // cyclically traverse
}
else
i = m + 1; // grow
p.index = i;
}
}
}
首先通过participant取得当前节点Node,然后根据当前节点Node的index去取arena中相对应的节点node。
前面提到过arena可以确保不同的slot在arena中是不会相冲突的,那么是怎么保证的呢?我们先看arena的创建:(见slotExchange()代码中)
arena = new Node[(FULL + 2) << ASHIFT];
static final int FULL = (NCPU >= (MMASK << 1)) ? MMASK : NCPU >>> 1;
private static final int ASHIFT = 7;
然后通过以下代码取得在arena中的节点:
Node q = (Node)U.getObjectVolatile(a, j = (i << ASHIFT) + ABASE);
ABASE = U.arrayBaseOffset(ak) + (1 << ASHIFT);
Class<?> ak = Node[].class;
U.arrayBaseOffset获取对象头长度,数组元素的大小可以通过unsafe.arrayIndexScale() 方法获取到。这也就是说要访问第N个元素的话,你的偏移量offset应该是arrayOffset+N*arrayScale。
其次我们回顾Node节点的定义,在Java 8中我们利用sun.misc.Contended来规避伪共享。所以说通过 << ASHIFT方式加上sun.misc.Contended,所以使得任意两个可用Node不会再同一个缓存行中。
在Node定义中有两个变量值得思考:bound以及collides。前面提到了数组area是为了避免竞争而产生的,如果系统不存在竞争问题,那么完全没有必要开辟一个高效的arena来徒增系统的复杂性。首先通过单个slot的exchanger来交换数据,当探测到竞争时将安排不同的位置的slot来保存线程Node,并且可以确保没有slot会在同一个缓存行上。如何来判断会有竞争呢?CAS替换slot失败,如果失败,则通过记录冲突次数来扩展arena的尺寸,我们在记录冲突的过程中会跟踪“bound”的值,以及会重新计算冲突次数在bound的值被改变时。
●我们再次回到arenaExchange()。取得arena中的node节点后,如果定位的节点q不为空,且CAS操作成功,则交换数据,返回交换的数据,唤醒等待的线程。
如果q为null且下标在bound & MMASK范围之内,则尝试占领该位置,如果成功,则采用自旋 + 阻塞的方式进行等待交换数据。
如果下标不在bound & MMASK范围之内 或者 由于q不为null但是竞争失败的时候:消除p。
●加入bound 不等于当前节点的bond(b != p.bound),则更新p.bound = b,collides = 0,i = m 或者 m – 1。
●如果冲突的次数不到m 或者 m已经为最大值 或者 修改当前bound的值失败,则通过增加一次collides以及循环递减下标i的值;否则更新当前bound的值成功:我们令i为m+1即为此时最大的下标。最后更新当前index的值。
系列文章传送门:
JUC探险-1、初识概貌
JUC探险-2、synchronized
JUC探险-3、volatile
JUC探险-4、final
JUC探险-5、原子类
JUC探险-6、Lock & AQS
JUC探险-7、ReentrantLock
JUC探险-8、ReentrantReadWriteLock
JUC探险-9、Condition
JUC探险-10、常见工具、数据结构
JUC探险-11、ConcurrentHashMap
JUC探险-12、CopyOnWriteArrayList
JUC探险-13、ConcurrentLinkedQueue
JUC探险-14、ConcurrentSkipListMap
JUC探险-15、BlockingQueue
JUC探险-16、ThreadLocal
JUC探险-17、线程池