- 第一个场景:
两个线程A、B;假设A线程要等待B线程执行完了之后才执行。在Thread类中提供了一个 join()方法很好能解决这个问题。
Thread.join()方法底层是等待 线程B dead过后才继续执行线程A
如果有这么一个情况,有1个主线程,5个主线程,需要5个子线程都完成里面特定的一个主逻辑之后就返回,告诉主线程自己完成了 , 并且这个时候子服务仍然可以继续执行
那么就引出了今天的主角1:
CountDownLatch
CountDownLatch是Java 1.5版本提供java.util.concurrent的一个类,解决多个线程之间的交互、通知等待这样问题的一个类
代码示例:
public class MyTest1 {
public static void main(String[] args) {
CountDownLatch countDownLatch=new CountDownLatch(3);// 参数是指实际执行子任务的数量
IntStream.range(0,3).forEach(i->new Thread(()->{
try {
Thread.sleep(2000);
System.out.println("Hello");
}catch (InterruptedException e){
e.printStackTrace();
}finally {
// countDown() 方法就会让 3 这个参数,原子性地减1
countDownLatch.countDown();
}
}).start());
System.out.println("========启动子线程完毕");
try {
//检测 CountDownLatch 传入的参数 3 是否大于0,大于0就会阻塞
// countDownLatch.await();
//重载方法可以很好的规避掉主线程 由于countDown()方法的一个调用不当,或者是失误,而导致无法退出,影响主线程的继续执行
countDownLatch.await(200, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-====主线程执行完毕");
}
}
值得注意的地方
1.首先 new CountDownLatch(3) 传入了一个参数,这个参数是指实际执行子任务的数量
2.countDown() 方法的调用放在了 finnally里面,保证了这个方法一定会被调用。每调用一次这个方法,方法就会让 3 这个参数,原子性地减1
3.await()方法会检测 CountDownLatch 传入的参数 3 是否大于0,大于0就会阻塞,(不可能会小于0),等于0的时候就会释放
4…await()方法有一个重载的方法 await(long timeout, TimeUnit unit) ;重载方法可以很好的规避掉主线程 由于countDown()方法的一个调用不当,或者是失误,而导致无法退出,影响主线程的继续执行
看看CountDownLatch 底层实现原理
内部内 Sync 继承了 AQS,使用了AQS 里面的tryAcquireSharedNanos 和releaseShared 方法。
哈哈,我大概就能理解到这儿了…
------------------------------------------------我是分隔线-------------------------------------------------------
------------------------------------------------我是分隔线-------------------------------------------------------
- 第二个场景:
假设有5个线程,(5个线程是同等级的),前面4个线程都到达了一个点,最后一个线程没有到达,那么前面4个线程就会陷入等待状态。直到最后一个线程都到达了这个点之后,这5个线程会同时开始去执行
那么就引出了今天的主角2:
CyclicBarrier (环形的屏障)
CyclicBarrier 是Java 1.5版本提供java.util.concurrent的一个类,解决多个线程之间的交互、通知等待这样的一个类,每个线程没有主次之分
public class MyTest2 {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, () -> {
System.out.println("Hello World");
}); //参数是3 是指参与方,意思就是参与到等待的线程的数量,当这三个线程都到达了屏障之前,屏障才会取消掉
for (int j = 0; j < 2; j++) {
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Thread.sleep((long) (Math.random() * 2000));
int randomInt = new Random().nextInt(500);
System.out.println("hello " + randomInt);
/**
* 1.检查当前屏障的前面是否有三个线程, 如果没有当前线程就会等待;
* 2.如果当最后一个线程在屏障前面调用 await方法的那一瞬间,算上自己已经有三个线程了,那么它会通知其余所有等待的线程
* 一起冲破屏障,然后一起执行
*/
cyclicBarrier.await();
System.out.println("world " + randomInt);
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
}
值得注意的地方
1.第一个构造方法 new CyclicBarrier(int parties) 有一个参数,参数是3 是指参与方,意思就是参与到等待的线程的数量,当这3个线程都到达了屏障之前,屏障才会取消掉
2.还有一个构造方法 new CyclicBarrier(int parties, Runnable barrierAction) barrierAction叫屏障动作这个参数。当最后一个线程到达屏障之前,就会触发这个参数的执行。在每个线程在执行自己的逻辑代码之前,注意是之前,就会执行barrierAction里面的代码
3.**cyclicBarrier.await()**方法的调用有两层意思
- 检查当前屏障的前面是否有三个线程, 如果没有当前线程就会等待;
- 如果当最后一个线程在屏障前面调用 await方法的那一瞬间,算上自己已经有三个线程了,那么它会通知其余所有等待的线程 一起冲破屏障,然后一起执行
4.有一个重载的方法 await(long timeout, TimeUnit unit),如果这个时间设置很短,不会像 CountDownLatch里面的 await带时间的重载方法。反而抛出异常
第一个异常是:TimeoutException,是等待超时异常,第二个异常:BrokenBarrierException,意思是屏障会被破坏
关于 CyclicBarrier 底层实现逻辑:
1.初始化 CyclicBarrier 中的各种成员变量,包括 parties、count以及 Runnable (可选)
2.当调用 await方法时,底层会先检查计数器是否已经归 0,如果是的话,那么就首先执行可选的 Runnable 接下来开始下一个 generation
3.在下一个分代中,将会重置 count值为 parties ,并且创建新的 Generation
4.同时会调用 Condition 的 signalAll 方法,唤醒所有在屏障前面的等待的线程,在屏障前进行等待
5.如果计数器没有归零,那么当前的调用线程将会通过 Condition 的 await方法,在屏障前进行等待
底层代码:(这里的 trip 是一个 Condition 的对象)
6.以上所有执行流程均在 lock 锁的控制范围内,不会出现并发安全的情况
底层代码:
CountDownLatch与CyclicBarrier对比理解!!
CyclicBarrier 构建方法里面的入参和 CountDownLatch 相比,CyclicBarrier的计数器是循环使用的,而CountDownLatch传参的计数器是一次性的
上诉 MyTest2代码用了一个双从 for循环就看到验证了这一点