JUC系列学习工具类CountDownLatch详解
作者:剑圣无痕 发布时间:2023-10-01 12:19:23
前言:
项目中我们经常会遇到有时候需要等待其他线程完成任务后,主线程才能执行其他任务,那么我们将如何实现呢?
Join 解决方案
join 的工作原理是,检查thread是否存活,如果存活则让当前线程永远wait,直到 thread线程终止,线程的 notifyAll才会被调用。
具体实现
public class JoinAThread extends Thread
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 线程开始");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() +
" 线程执行完毕");
}
}
public class JoinBThread extends Thread
{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() +
" 线程开始");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName() +
" 线程执行完毕");
}
}
public class JoinTest
{
public static void main(String[] args) throws InterruptedException
{
JoinAThread joinA =new JoinAThread();
Thread threadA =new Thread(joinA,"线程A");
JoinBThread joinB =new JoinBThread();
Thread threadB =new Thread(joinB,"线程B");
threadA.start();
threadB.start();
threadA.join();
threadB.join();
System.out.println("子线程执行完成了,主线程"+Thread.currentThread().getName()+"开始执行了");
}
}
执行结果
从结果中,我们可以看出只有子线程执行完成了,主线程才开始执行。join的实现我们需要每个线程进行join,如果存在多个线程,那么写起来会比较的繁琐,那么又没更新优化的方案了,答案是JUC下面的工具类CountDownLatch,也能完成同样的功能。
CountDownLatch 解决方案
具体实现
public class CountDownLatchTest
{
private static Logger logger =LoggerFactory.getLogger(CountDownLatchTest.class);
public static void main(String[] args) throws InterruptedException
{
ExecutorService exec = Executors.newCachedThreadPool();
final CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 1; i <= 10; i++){
exec.execute(() -> {
try {
invokeServiec();
} catch (InterruptedException e)
{
logger.info("invoce service error",e);
}
finally
{
//计数器减一
countDownLatch.countDown();
}
});
}
countDownLatch.await();
logger.info("所有的子线程执行完成,主线程"+Thread.currentThread().getName()+"开始执行");
}
private static void invokeServiec() throws InterruptedException
{
logger.info(Thread.currentThread().getName()+",开始执行任务");
Thread.sleep(300);
}
}
说明:CountDownLatch中有两个方法一个是await()方法,调用这个方法的线程会被阻塞,另外一个是countDown() 方法,调用此方法会使计数器减一,当计数器的值为0时,调用await()方法被阻塞的线程才会被唤醒。
执行结果:
原理说明
CountDownLatch 是一个计数器闭锁,通过它可以完成类似于阻塞当前线程的功能,即:一个线程或多个线程一直等待,直到其他线程执行的操作完成。
基本原理
CountDownLatch
CountDownLatch内部定义计数器和一个队列。当计数器的值递减为0之前,阻塞队列里面的线程处于挂起状态,当计数器递减到0时会唤醒阻塞队列所有线程,计数器是一个标志,可以表示一个任务一个线程,也可以表示一个倒计时器。
常用的方法
countDown:用于使计数器减一,其一般是执行任务的线程调用. await: 使用线程处于等待状态,其一般是主线程调用.
countDown
countDown实现方法如下:
说明:sync是一个AQS的队列,调用的为AQS的releaseShared方法,其具体实现如下:
而releaseShared调用为CountDownLatch中的内部类sync中的tryReleaseShared方法,具体实现如下:
tryReleaseShared(int)方法即对state属性进行减一操作的代码.通过CAS进行减操作来保证原子性,其会比较state是否为c,如果是则将其设置为nextc(自减1),如果state不为c,则说明有另外的线程在getState()方法和compareAndSetState()方法调用之间对state进行了设置,当前线程也就没有成功设置state属性的值,其会进入下一次循环中,如此往复,直至其成功设置state属性的值,即countDown()方法调用成功。
而doReleaseShared方法调用的为AbstractQueuedSynchronizer简称AQS的doReleaseShared方法,
说明:首先判断头结点不为空,且不为尾节点,说明等待队列中有等待唤醒的线程,这里需要说明的是,在等待队列中,头节点中并没有保存正在等待的线程,其只是一个空的Node对象,真正等待的线程是从头节点的下一个节点开始存放的,因而会有对头结点是否等于尾节点的判断。在判断等待队列中有正在等待的线程之后,其会清除头结点的状态信息,并且调用unparkSuccessor(Node)方法唤醒头结点的下一个节点,使其继续往下执行。如下是unparkSuccessor(Node)方法的具体实现:
可以看到,unparkSuccessor(Node)方法的作用是唤醒离传入节点最近的一个处于等待状态的线程,使其继续往下执行。
await
await方法实现如下:
await()方法调用了Sync对象的方法acquireSharedInterruptibly(int)方法,该方法的具体实现如下:
在doAcquireSharedInterruptibly(int)方法中,首先使用当前线程创建一个共享模式的节点。然后在一个for循环中判断当前线程是否获取到执行权限,如果有(r >= 0判断)则将当前节点设置为头节点,并且唤醒后续处于共享模式的节点;如果没有,则对调用shouldParkAfterFailedAcquire(Node, Node)和parkAndCheckInterrupt()方法使当前线程处于"搁置"状态,该"搁置"状态是由操作系统进行的,这样可以避免该线程无限循环而获取不到执行权限,造成资源浪费,这里也就是线程处于等待状态的位置,也就是说当线程被阻塞的时候就是阻塞在这个位置。当有多个线程调用await()方法而进入等待状态时,这几个线程都将等待在此处。
来源:https://juejin.cn/post/7132484030544478222


猜你喜欢
- JetBrains 系列产品(IDEA、Pycharm 等)使用本站破解教程 (opens new window),在输入激活码时,部分小伙
- 1.查找概述查找表: 所有需要被查的数据所在的集合,我们给它一个统称叫查找表。查找表(Search Table)是由同一类型的数据元素(或记
- [LeetCode] 131.Palindrome Partitioning 拆分回文串Given a string s, par
- Linux下JDK安装教程,具体内容如下1、下载 JDK Linux 版本(注意看自己安装 Linux 系统的位数)oracle 官网下载地
- 前言本文主要演示一个普通 java 项目导入IDEA的流程步骤及可能出现的问题、原因及解决办法。本文使用的部分软件版本如下:IDEA 201
- 从不规则的字符串中截取出日期最近在项目中需要远程调接口,从String字符串中截取出日期,想了好久,最后用java8新特性,解决了,java
- 经典的Java基础面试题集锦,欢迎收藏和分享。问题:如果main方法被声明为private会怎样?答案:能正常编译,但运行的时候会提示”ma
- Java 读取外部资源的方法详解在Java代码中经常有读取外部资源的要求:如配置文件等等,通常会把配置文件放在classpath下或者在we
- 近来复习数据结构,自己动手实现了栈。栈是一种限制插入和删除只能在一个位置上的表。最基本的操作是进栈和出栈,因此,又被叫作“先进后出”表。首先
- 使用commons的jexl可实现将字符串变成可执行代码的功能,我写了一个类来封装这个功能:import java.util.Map;imp
- 主要为以下实现步骤:1.绑定域名先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”。(特别提示不需要加上http或
- public void ProcessRequest (HttpContext context) { &n
- springboot配置mysql数据库spring.datasource.url报错spring.datasource.url=jdbc:
- 在一个项目中我们可能会需要用到相同的布局设计,如果都写在一个xml文件中,代码显得很冗余,并且可读性也很差,所以我们可以把相同布局的代码单独
- 本文实例为大家分享了安卓Button按钮的四种点击事件,供大家参考,具体内容如下第一种:内部类实现 1.xml里面先设置Button属性&l
- 近日,Eclipse经常挂掉,都是由于JVM崩溃的原因。每次都有以下错误日志:## A fatal error has been detec
- 缓存可以说是加速服务响应速度的一种非常有效并且简单的方式。在缓存领域,有很多知名的框架,如EhCache 、Guava、HazelCast等
- 效果图: //偶数随机 Random evenRanm
- 定义单一职责原则(Single Responsibility Principle, SRP),有且仅有一个原因引起类的变更。简单来说,就是针
- // 获取国家省市区信息$(document).ready(function(){//从程序