并发包(JUC)之Condition、CountDownLatch
Condition
1、Condition概述
对于synchronized,可以结合wait/notify实现线程的通信,Condition则是JUC中提供的一个多线程协调通信的工具类,可以让某些线程等待某个条件(condition),只有满足条件时才会被唤醒。
condition.await( )和condition.signal( )使用示例:和wait/notify一样,await/signal也要在lock( )和unlock( )之间使用。
public class CarOperation { private int flag = 1; private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition(); public void fuelUp(int num){ lock.lock(); try { while (flag != 1){ c1.await(); } System.out.println("第"+num+"辆车开始加油"); flag = 2; c2.signal(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void carWash(int num){ lock.lock(); try { while (flag != 2){ c2.await(); } System.out.println("第"+num+"辆车开始清洗"); flag = 3; c3.signal(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public void drive(int num){ lock.lock(); try { while (flag != 3){ c3.await(); } System.out.println("第"+num+"辆车驶离"); flag = 1; c1.signal(); }catch (InterruptedException e){ e.printStackTrace(); }finally { lock.unlock(); } } public static void main(String[] args) { CarOperation operation = new CarOperation(); new Thread(()->{ for(int i=0;i<4;i++){ operation.fuelUp(i); } },"fuelUp").start(); new Thread(()->{ for(int i=0;i<4;i++){ operation.carWash(i); } },"carWash").start(); new Thread(()->{ for(int i=0;i<4;i++){ operation.drive(i); } },"drive").start(); } }
输出结果:
第0辆车开始加油
第0辆车开始清洗
第0辆车驶离
第1辆车开始加油
第1辆车开始清洗
第1辆车驶离
第2辆车开始加油
第2辆车开始清洗
第2辆车驶离
第3辆车开始加油
第3辆车开始清洗
第3辆车驶离
2、Condition原理分析
public final void await() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); //将当前线程封装成node加入等待队列尾部 Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; //检测此节点的线程是否在同步队上,如果不在,则说明该线程还不具备竞争锁的资格,则继续等待直到检测到此节点在同步队列上 while (!isOnSyncQueue(node)) { //当node处于等待队列时,挂起当前线程。 LockSupport.park(this); //如果发生了中断,则跳出循环,结束等待 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; } //被唤醒后该节点一定会在AQS队列上, //之前分析过acquireQueued方法获取不到锁会继续阻塞 //获取到了锁,中断过返回true,未中断过返回false //获取到锁存在中断并且不是中断唤醒的线程将中断模式设置为重新中断 if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled //清除条件队列中所有状态不为 CONDITION 的结点 unlinkCancelledWaiters(); if (interruptMode != 0) reportInterruptAfterWait(interruptMode); }
调用Condition,需要获得Lock锁,所以意味着会存在一 个AQS同步队列,而Condition也是通过AbstractQueuedSynchronizer内部的ConditionObject具体实现的,其内部维护一个单向链表(也成为Condition队列)。
假设有线程A和线程B状态如下:
(1)当在线程A中调用condition.await( ),将ThreadA封装成Node节点(waitStatus=CONDITION),将Node添加到Condition队列尾部,然后释放锁(此时AQS中state=0),通过LockSupport.park( )阻塞在调用处。此时AQS队列挂起的线程被唤醒竞争锁。
注意:添加到等待队列前,会先判断线程是否在同步队列中,不再的话会将当前线程先挂起,知道当前线程在同步队列中才能加入到等待队列中
(2)当在线程B中调用condition.signal( )(注意要和A中调用的condition为同一个对象),将Condition队列的头节点从等待队列中移除,firstWaiter指向原头节点的后继节点,此时原头节点有资格去竞争锁。
推荐文章:https://mp.weixin.qq.com/s?__biz=MzI5MDg2NjEzNA==&mid=2247489175&idx=1&sn=607a177490c5f59f309d711036f2a21e&chksm=ec18002edb6f8938fd03ad1191a0ea55961f485e6b38409ba80910c87e40440829ce78de9d16&cur_album_id=2627239089976950785&scene=189#wechat_redirectCountDownLatch
1、CountDownLatch(共享锁)
countdownlatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程做操作执行完。提供了两个方法,一个是await( ),一个是countDown( ),countdownlatch初始化时需要传入一个整数,在这个整数倒数到0之前,调用了await方法的线程都要等待,然后通过countDwon方法来倒数。
使用示例:
public class Demo extends Thread{
static CountDownLatch countDownLatch=new CountDownLatch(1);
@Override public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
for(int i=0;i<1000;i++){
new Demo().start();
}
countDownLatch.countDown();
}
}
从示例可以看出,countDownLatch可以做压测。每次调用countDown( ),state会减1,当state减到0时之前调用await阻塞的线程都被唤醒执行。
2、CountDownLatch原理
countDownLatch也使用到了AQS队列,当一个线程内调用await( )时,会将该节点封装成Node(waitStatus=PROPAGATE,共享模式下才节点才会处于这个状态,处于这个状态下的节点,会对线程的唤醒进行传播)添加到AQS队列中,并为阻塞状态。当倒数到state=0时,会从头节点依次唤醒AQS队列中的所有节点。
其他工具类:
1、Semaphore,可以控制同时访问的线程个数,常用于限流功能。
使用示例:
public class Test { 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; } public void run(){ try { semaphore.acquire();// 获取一个许可 System.out.println("第"+num+"占用 一个停车位"); TimeUnit.SECONDS.sleep(2); System.out.println("第"+num+"俩车 走喽"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
2、CyclicBarrier,可循环使用的屏障,让一组线程到达一个屏障时被阻塞,直到最后一个线程到达屏障时,屏障才会打开,所有被屏障拦截的线程才会继续工作。