1、简介
本文将分析 java.util.concurrent 包下的同步组件CountDownLatch和CyclicBarrier、Semaphore。通过分析这三个同步组件,可使我们对 Java 线程间协同有更深入的了解。
-
CountDownLatch 允许一个或一组线程等待其他线程完成后再恢复运行。线程可通过调用await方法进入等待状态,在其他线程调用countDown方法将计数器减为0后,处于等待状态的线程即可恢复运行。
-
CyclicBarrier (可循环使用的屏障)则与此不同,CyclicBarrier 允许一组线程到达屏障后阻塞住,直到最后一个线程进入到达屏障,所有线程才恢复运行。它们之间主要的区别在于唤醒等待线程的时机。CountDownLatch 是在计数器减为0后,唤醒等待线程。CyclicBarrier 是在计数器(等待线程数)增长到指定数量后,再唤醒等待线程。
-
Semaphore(信号量)是一个控制访问多个共享资源的计数器,和CountDownLatch一样,其本质上是一个“共享锁”。Semaphore一个计数信号量。在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
2、原理
2.1 CountDownLatch
CountDownLatch 的同步功能是基于 AQS 实现的,CountDownLatch 使用 AQS 中的 state 成员变量作为计数器。在 state 不为0的情况下,凡是调用 await 方法的线程将会被阻塞,并被放入 AQS 所维护的同步队列中进行等待。
每个阻塞的线程都会被封装成节点对象,节点之间通过 prev 和 next 指针形成同步队列。初始情况下,队列的头结点是一个虚拟节点。该节点仅是一个占位符,没什么特别的意义。每当有一个线程调用 countDown 方法,就将计数器 state–。当 state 被减至0时,队列中的节点就会按照 FIFO 顺序被唤醒,被阻塞的线程即可恢复运行。
2.2 CyclicBarrier
CyclicBarrier 并没有直接通过 AQS 实现同步功能,而是在重入锁 ReentrantLock 的基础上实现的。在 CyclicBarrier 中,线程访问 await 方法需先获取锁才能访问。在最后一个线程访问 await 方法前,其他线程进入 await 方法中后,会调用 Condition 的 await 方法进入等待状态。在最后一个线程进入 CyclicBarrier await 方法后,该线程将会调用 Condition 的 signalAll 方法唤醒所有处于等待状态中的线程。同时,最后一个进入 await 的线程还会重置 CyclicBarrier 的状态,使其可以重复使用。
在创建 CyclicBarrier 对象时,需要转入一个值,用于初始化 CyclicBarrier 的成员变量 parties,该成员变量表示屏障拦截的线程数。当到达屏障的线程数小于 parties 时,这些线程都会被阻塞住。当最后一个线程到达屏障后,此前被阻塞的线程才会被唤醒。
2.3 Semaphore
Semaphore是一个非负整数(>=1)。当一个线程想要访问某个共享资源时,它必须要先获取Semaphore,当Semaphore >0时,获取该资源并使Semaphore – 1。如果Semaphore值 = 0,则表示全部的共享资源已经被其他线程全部占用,线程必须要等待其他线程释放资源。当线程释放资源时,Semaphore则+1。
3、源码解析
3.1 CountDownLatch
3.1.1 构造方法
// 内部实现AQS抽象类
private static final class Sync extends AbstractQueuedSynchronizer {
// 设置线程状态(数)
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
// 尝试在共享状态下获取同步状态
protected int tryAcquireShared(int acquires) {
// 如果 state = 0,则返回1,表明可获取同步状态
return (getState() == 0) ? 1 : -1;
}
// 尝试在共享状态下释放同步状态
protected boolean tryReleaseShared(int releases) {
for (;;) {
int c = getState();
// state=0没有可以释放的
if (c == 0)
return false;
int nextc = c-1;
// cas释放
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
private final Sync sync;
public CountDownLatch(int count) {
// state不能设置小于0
if (count < 0)
throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
3.1.2 await
// 该方法会使线程进入等待状态,直到计数器减至0,或者线程被中断
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
// 若线程被中断,则直接抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
// 调用 Sync 中覆写的 tryAcquireShared 方法,尝试获取同步状态
if (tryAcquireShared(arg) < 0)
// 调用内部类Syn的tryAcquireShared()
// 获取失败,将线程放入 AQS 的同步队列中进行等待
doAcquireSharedInterruptibly(arg);
}
- 使用demo
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName());
countDownLatch.countDown();
}, i+"").start();
}
countDownLatch.await();
System.out.println("execute finished..........");
3.1.3 countDown
public void countDown() {
sync.releaseShared(1);
}
// AQS
public final boolean releaseShared(int arg) {
// 调用内部类Syn的tryReleaseShared()
if (tryReleaseShared(arg)) {
// tryReleaseShared 返回 true 时,表明 state = 0,即计数器为0
// doReleaseShared 方法唤醒正在同步队列中等待的线程
doReleaseShared();
return true;
}
return false;
}
3.2 CyclicBarrier
栅栏用于等待其它线程,且会阻塞自己当前线程;
所有线程必须同时到达栅栏位置后,才能继续执行;
3.2.1 属性及构造方法
private static class Generation {
// 用于记录屏障有没有被破坏
boolean broken = false;
}
private Generation generation = new Generation();
private final ReentrantLock lock = new ReentrantLock();
private final Condition trip = lock.newCondition();
// 线程数,即当 parties 个线程到达屏障后,屏障才会放行
private final int parties;
// 回调对象,如果不为 null,会在第 parties 个线程到达屏障后被执行
private final Runnable barrierCommand;
// 当 count > 0 时,到达屏障的线程会进入等待状态。
// 当最后一个线程到达屏障后,count 自减至0。
// 最后一个到达的线程会执行回调方法,并唤醒其他处于等待状态中的线程。
private int count;
//指的是需要几个线程一起到达,才可以使所有线程取消等待
public CyclicBarrier(int parties) {
this(parties, null);
}
public CyclicBarrier(int parties, Runnable barrierAction) {
if (parties <= 0) throw new IllegalArgumentException();
this.parties = parties;
this.count = parties;
this.barrierCommand = barrierAction;
}
3.2.2 await
public int await() throws InterruptedException, BrokenBarrierException {
try {
return dowait(false, 0L);
} catch (TimeoutException toe) {
throw new Error(toe); // cannot happen
}
}
private int dowait(boolean timed, long nanos)
throws InterruptedException, BrokenBarrierException,
TimeoutException {
final ReentrantLock lock = this.lock;
lock.lock();
try {
final Generation g = generation;
// 如果 g.broken = true
// 表明屏障被破坏了,这里直接抛出异常
if (g.broken)
throw new BrokenBarrierException();
// 如果线程中断,则调用 breakBarrier 破坏屏障
if (Thread.interrupted()) {
breakBarrier();
throw new InterruptedException();
}
// index 表示线程到达屏障的顺序,0最后一个到达
int index = --count;
if (index == 0) { // tripped
boolean ranAction = false;
try {
final Runnable command = barrierCommand;
// 执行回调
if (command != null)
command.run();
ranAction = true;
// 重置屏障状态,使其进入新一轮的运行过程中
nextGeneration();
return 0;
} finally {
if (!ranAction)
breakBarrier();
}
}
// 线程运行到此处的线程都会被屏障挡住,并进入等待状态
for (;;) {
try {
if (!timed)
// 未超时,加入condition队列
trip.await();
else if (nanos > 0L)
nanos = trip.awaitNanos(nanos);
} catch (InterruptedException ie) {
// 本轮运行还未结束。此时调用 breakBarrier 破坏屏障,唤醒其他线程,并抛出异常
if (g == generation && ! g.broken) {
breakBarrier();
throw ie;
} else {
Thread.currentThread().interrupt();
}
}
// 屏障被破坏,则抛出 BrokenBarrierException 异常
if (g.broken)
throw new BrokenBarrierException();
// 屏障进入新的运行轮次,此时返回线程在上一轮次到达屏障的顺序
if (g != generation)
return index;
if (timed && nanos <= 0L) {
breakBarrier();
throw new TimeoutException();
}
}
} finally {
lock.unlock();
}
}
// 破坏屏障
private void breakBarrier() {
generation.broken = true;
count = parties;
trip.signalAll();
}
// 开启新的一轮运行过程
private void nextGeneration() {
trip.signalAll();
count = parties;
generation = new Generation();
}
3.2.3 reset
// reset 方法用于强制重置屏障,使屏障进入新一轮的运行过程中。
public void reset() {
final ReentrantLock lock = this.lock;
lock.lock();
try {
// 破坏屏障
breakBarrier();
// 开启新一轮的运行过程
nextGeneration();
} finally {
lock.unlock();
}
}
循环栅栏(分批等待执行):
CyclicBarrier cyclicBarrier = new CyclicBarrier(2);
Executor executor = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS,
new ArrayBlockingQueue(2));
executor.execute(new Thread(() -> {
System.out.println("start thread: " + Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("thread execute finished: " + Thread.currentThread().getName());
}, "1"));
executor.execute(new Thread(() -> {
System.out.println("start thread: " + Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("thread execute finished: " + Thread.currentThread().getName());
}, "2"));
executor.execute(new Thread(() -> {
System.out.println("start thread: " + Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("thread execute finished: " + Thread.currentThread().getName());
}, "3"));
executor.execute(new Thread(() -> {
System.out.println("start thread: " + Thread.currentThread().getName());
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println("thread execute finished: " + Thread.currentThread().getName());
}, "4"));
3.3 Semaphore
以Semaphore为例,其内部维护一组资源,可以通过构造函数指定数目,其它线程在执行的时候,可以通过acquire方法获取资源,有的话,继续执行(使用结束后释放资源),没有资源的话将阻塞直到有其它线程调用release方法释放资源;用来控制同一时间,资源可被访问的线程数量.
3.3.1 属性及内部类
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// 设置state
Sync(int permits) {
setState(permits);
}
final int getPermits() {
return getState();
}
// 以共享方式获取锁
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
int available = getState();
int remaining = available - acquires;
// 可用数,小于0返回,否则更新
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
// 以共享方式释放锁
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current)
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
// 减少可执行线程数
final void reducePermits(int reductions) {
for (;;) {
int current = getState();
int next = current - reductions;
if (next > current) // underflow
throw new Error("Permit count underflow");
if (compareAndSetState(current, next))
return;
}
}
final int drainPermits() {
for (;;) {
int current = getState();
if (current == 0 || compareAndSetState(current, 0))
return current;
}
}
}
// 非公平
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -2694183684443567898L;
NonfairSync(int permits) {
super(permits);
}
//不需要判断当前线程是否位于CLH同步队列列头
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
}
}
// 公平锁
static final class FairSync extends Sync {
FairSync(int permits) {
super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
//判断该线程是否位于CLH队列的列头
if (hasQueuedPredecessors())
return -1;
int available = getState();
//设置“获得acquires个信号量许可之后,剩余的信号量许可数”
int remaining = available - acquires;
if (remaining < 0 ||
//CAS设置信号量
compareAndSetState(available, remaining))
return remaining;
}
}
}
- 构造方法
// 创建具有给定的许可数和是否公平
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
// 默认为非公平
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
- cas 源码
public final native boolean compareAndSwapObject(java.lang.Object o, long l, java.lang.Object o1, java.lang.Object o2);
1: UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapObject(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jobject e_h, jobject x_h))
2: UnsafeWrapper("Unsafe_CompareAndSwapObject");
3: oop x = JNIHandles::resolve(x_h); //待更新的新值,也就是UpdateValue
4: oop e = JNIHandles::resolve(e_h); //期望值,也就是ExpectValue
5: oop p = JNIHandles::resolve(obj); //待操作对象
6: HeapWord* addr = (HeapWord *)index_oop_from_field_offset_long(p, offset);//根据操作的对象和其在内存中的offset,计算出内存中具体位置
7: oop res = oopDesc::atomic_compare_exchange_oop(x, addr, e, true);// 如果操作对象中的值和e期望值一致,则更新存储值为x,反之不更新
8: jboolean success = (res == e);
9: if (success) //满足更新条件
10: update_barrier_set((void*)addr, x); // 更新存储值为x
11: return success;
12: UNSAFE_END
3.3.2 信号量获取
public void acquire(int permits) throws InterruptedException {
if (permits < 0) throw new IllegalArgumentException();
sync.acquireSharedInterruptibly(permits);
}
// AQS模板方法
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
3.3.3 信号量释放
public void release() {
sync.releaseShared(1);
}
// AQS模板方法
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
- demo
Semaphore semaphore = new Semaphore(5);
ExecutorService executorService = newFixedThreadPool(20);
for (int i = 0; i < 20; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
System.out.println("get acquire:" + Thread.currentThread().getName());
Thread.sleep(2000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
4、总结
- AQS
在AQS中存在一个变量state,当我们创建Semaphore对象传入许可数值时,最终会赋值给state,state的数值代表同一个时刻可同时操作共享数据的线程数量,每当一个线程请求获取同步状态成功,state的值将会减少1,直到state为0时,表示已没有可用的许可数,也就是对共享数据进行操作的线程数已达到最大值,其他后来线程将被阻塞,此时AQS内部会将线程封装成共享模式的Node结点,加入同步队列中等待并开启自旋操作。只有当持有对共享数据访问权限的线程执行完成任务并释放同步状态后,同步队列中的对于的结点线程才有可能获取同步状态并被唤醒执行同步操作,注意在同步队列中获取到同步状态的结点将被设置成head并清空相关线程数据。
- ReentrantLock
state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
- CountDownLatch
任务分为N个子线程去执行,state也初始化为N。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS(Compare and Swap)减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
- Semaphore(信号量)
允许多个线程同时访问: synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源, 基于AQS实现的,在构造的时候会设置一个值,代表着资源数量。信号量主要是应用于是用于多个共享资源的互斥使用,和用于并发
线程数的控制,信号量也分公平和非公平的情况,基本方式和reentrantLock差不多,在请求资源调用task时,会用自旋的方式减1,如果成功,则获取成功了,如果失败,导致资源数变为了0,就会加入队列里面去等待。调用release的时候会加一,补充资源,并唤醒等待队列。
- CyclicBarrier(循环栅栏)
CyclicBarrier 和 CountDownLatch 非常类似,它也可以实现线程间的技术等待,但是它的功能比 CountDownLatch 更加复杂和强大。主要应用场景和CountDownLatch 类似。主要是等待一组线程到底同一个状态的时候,放闸。CyclicBarrier还可以传递一个Runnable对象,可以到放闸的时候,执行这个任务。CyclicBarrier是可循环的,当调用await的时候如果count变成0了则会重置状态,如何重置呢,CyclicBarrier新增了一个字段parties,用来保存初始值,当count变为0的时候,就重新赋值。还有一个不同点,CyclicBarrier不是基于AQS的,而是基于RentrantLock实现的。存放的等待队列是用了条件变量的方式。