Condition
Condition是JUC里面多线程协调通信的工具类,可以让一些线程一起等待条件即(Condition)。只有当满足条件时,线程才会被唤醒。
类似于 wait/notify 。在Condition中也有类似的实现。
public class ConditionAwait implements Runnable{
private Lock lock;
private Condition condition;
public ConditionAwait(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("begin -- ConditionAwait start");
lock.lock();
try {
// 阻塞挂起当前线程
condition.await();
System.out.println("end -- ConditionAwait");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ConditionSignal implements Runnable{
private Lock lock;
private Condition condition;
public ConditionSignal(Lock lock, Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("begin -- condition signal");
lock.lock();
// 唤醒阻塞状态的线程 signal 唤醒一个 signal 唤醒所有
condition.signal();
System.out.println("end -- condition signal");
lock.unlock();
}
}
public static void main(String[] args) {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(new ConditionAwait(lock, condition)).start();
new Thread(new ConditionSignal(lock, condition)).start();
}
// 输出
begin -- ConditionAwait start
begin -- condition signal
end -- condition signal
end -- ConditionAwait
如代码示例,我们启动了两个线程。两个线程使用同一个Lock和Condition对象。通过代码的运行结果可以发现。当代码运行到condition.await()时,线程将会被挂起。等待另一个线程调用condition.signal()方法时,将会去唤醒被阻塞的线程。值得注意的时,阻塞在condition.await()方法处的线程,若没有condition.signal()方法和condition.signalAll()方法去唤醒,线程将一直处于被阻塞状态。
Condition源码分析
调用Lock.lock(),需要获得Lock锁,所以意味着会存在一个AQS同步队列。初始情况如下
线程A获得锁后,state状态由0更新到1。那么此时线程A调用了condition.await()方法后。会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。
await()方法
public final void await() throws InterruptedException {
// 表示调用await方法允许被中断
if (Thread.interrupted())
throw new InterruptedException();
// 创建一个新的节点,节点状态为condition,该方法构造了一个新的 condition 队列
// 这里的队列不是双向链表,而是单向链表
Node node = addConditionWaiter();
// 释放当前的锁,得到锁的状态,并从AQS队列中唤醒head节点的next节点的线程
// 彻底释放锁
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断当前节点是否在同步队列上,当还没有收到 signal 信号,则将当前线程阻塞
while (!isOnSyncQueue(node)) {
// 挂起当前线程
LockSupport.park(this);
//
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 当线程醒来,会尝试获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
// 清理 condition 队列上的 node
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
当addConditionWaiter()方法执行完毕,会构造一个 Condition 队列。此时线程A会被加入到condition队列中。直到被其它线程用signal唤醒,会重新加入到AQS队列中去竞争锁。
signal()方法
await()方法会阻塞ThreadA,然后ThreadB抢占到了锁的执行权限,ThreadB中调用了Condition的signal方法。将会唤醒在condition队列中的节点。
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 拿到condition队列中的一个节点
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal()方法
private void doSignal(Node first) {
do {
// 从 condition 队列中删除 first 节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
// CAS修改节点状态,将这个节点放到 AQS 队列中。然后唤醒这个节点的线程。
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
CountDownLatch
countDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其它线程执行完毕再执行。
public void test() throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行中");
// 调用countDown方法,计数器减 1 等于 2
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"执行完毕");
}, "t1").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行中");
// 调用countDown方法,计数器减 1 等于 1
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"执行完毕");
}, "t2").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"执行中");
// 调用countDown方法,计数器减 1 等于 0
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"执行完毕");
}, "t3").start();
// 使用 countDownLatch。await() 方法来阻塞主线程
// 当 CountDownLatch 计数器的值为 0 的时候 主线程才会被释放
countDownLatch.await();
System.out.println("所有线程执行完毕");
}
使用CountDownLatch模拟并发场景。
public class ConcurrenceDemo extends Thread{
private static CountDownLatch countDownLatch = new CountDownLatch(1);
@Override
public void run() {
try {
// 阻塞当前线程
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("ThreadName:"+Thread.currentThread().getName());
}
public static void main(String[] args) {
// 创建一千个线程,同时被阻塞
for (int i = 0; i < 1000; i++) {
new ConcurrenceDemo().start();
}
// 计数器-1 所有线程同时被释放
countDownLatch.countDown();
}
}
总结:countDown()方法每次调用都会将state的值减1,直到state的值为0;而await是一个阻塞方法,当state减为0的时候,await方法才会返回。所有调用了await方法的线程阻塞在AQS的阻塞队列中,等待被唤醒。
Semaphore
semaphore,可以控制同时访问的线程个数。
如下代码,我们创建Semaphore对象,设置参数为5,表示同时允许5个线程执行目标代码。
public class SemaphoreDemo {
public static void main(String[] args) {
// 表示同时可以有五个线程去获得执行权
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Car(i, semaphore).start();
}
}
static class Car extends Thread {
private int num;
private Semaphore semaphore;
public Car(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
// 获取一个许可
semaphore.acquire();
System.out.println("第"+num+"占用一个停车位");
TimeUnit.SECONDS.sleep(2);
System.out.println("第"+num+"辆车走了");
semaphore.release();
} catch (Exception e) {
}
}
}
}
使用场景:比较适合用来做限流操作。
CyclicBarrier
CyclicBarrier的字面意思是可循环使用(cyclic)的屏障(Barrier)。该工具的作用是让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,然后所有被拦截的屏障才会继续工作。CyclicBarrier的默认构造方法是CyclicBarrier(int parties),其参数表示屏障拦截线程的数量。
使用场景:当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用CyclicBarrier。