详解Java线程同步器CountDownLatch
作者:java小新人 发布时间:2023-08-23 18:42:39
标签:Java,线程同步器,CountDownLatch
Java程序有的时候在主线程中会创建多个线程去执行任务,然后在主线程执行完毕之前,把所有线程的任务进行汇总,以前可以用线程的join方法,但是这个方法不够灵活,我们可以使用CountDownLatch类,实现更优雅,而且使用线程池的话,可没有办法调用线程的join方法的呀!
一.简单使用CountDownLatch
直接使用线程:
package com.example.demo.study;
import java.util.concurrent.CountDownLatch;
public class Study0215 {
//这里相当于新建一个初始值为2的计数器
private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
Thread.sleep(1000);
System.out.println("线程一执行完毕");
} catch (Exception e) {
}finally {
//每调用这个方法计数器减一
countDownLatch.countDown();
}
}).start();
new Thread(()->{
try {
Thread.sleep(1000);
System.out.println("线程二执行完毕");
} catch (Exception e) {
}finally {
countDownLatch.countDown();
}
}).start();
System.out.println("两个线程已经全部启动");
//只要调用了这个方法之后,主线程会阻塞,直到计数器countDownLatch变成0就会返回
countDownLatch.await();
System.out.println("执行完毕");
}
}
实际中尽量少直接操作线程,而是使用线程池:
package com.example.demo.study;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Study0215 {
// 这里相当于新建一个初始值为2的计数器
private static volatile CountDownLatch countDownLatch = new CountDownLatch(2);
public static void main(String[] args) throws InterruptedException {
//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
//将任务一丢进线程池
pool.submit(() -> {
try {
Thread.sleep(1000);
System.out.println("线程一执行完毕");
} catch (Exception e) {
} finally {
// 每调用这个方法计数器减一
countDownLatch.countDown();
}
});
//任务二丢进线程池
pool.submit(() -> {
try {
Thread.sleep(1000);
System.out.println("线程二执行完毕");
} catch (Exception e) {
} finally {
countDownLatch.countDown();
}
});
System.out.println("两个线程已经全部启动");
// 只要调用了这个方法之后,主线程会阻塞,直到计数器countDownLatch变成0就会返回
countDownLatch.await();
System.out.println("执行完毕");
}
}
二.await方法
看下面的图,可以知道这个CountDownLatch类内部有个工具类Sync实现了AQS,然后CountDownLatch中的方法都是调用工具类Sync去操作的,emmm....跟前面说过的ReentrantLock类结构是一样的;
我们看看CountDownLatch构造器传递的数其实就是设置AQS中state的值:
//实际上调用把值传递给了Sync,也就是设置了AQS中的state
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
Sync(int count) {
setState(count);
}
我们再看看await方法:
//当前线程调用了await方法之后,当前线程就会给阻塞,直到以下两种情况:
//1.其他线程调用了countDown方法将计数器减到0之后,该线程就返回了;
//2.其他线程调用了当前的线程的中断方法,当前线程抛出异常InterruptedException
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
//当前线程被中断就抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//查看计数器中的值是不是0,不过不是0,就进入AQS等待队列等待;
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
三.countDown方法
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//tryReleaseShared方法返回false,说明当前计数器的值减一成功
//返回true,说明计数器的值此时为0,那就要唤醒因为调用了CountDownLatch而阻塞的线程
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
//一个无限循环
for (;;) {
//获取state的值
int c = getState();
//如果state为0,返回false
if (c == 0)
return false;
//否则就把state减一然后用CAS更新到state
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
四.getState方法
这个方法获取计数器的值,其实就是获取AQS中的state的值;
int getCount() {
return getState();
}
protected final int getState() {
return state;
}
其实CountDownLatch比较容易,功能和Thread的join方法一样,只不过更灵活,基于AQS实现,在初始化的时候设置state的值,当线程调用CountDownLatch的await方法的时候,当前线程就会被丢到AQS的阻塞队列挂起;然后当其他线程调用了countDown方法,其实就是将state减一,当state等于0的时候,就会唤醒所有因为调用await方法而阻塞的线程;
来源:https://www.cnblogs.com/wyq1995/p/12315072.html


猜你喜欢
- IO感觉上和多线程并没有多大关系,但是NIO改变了线程在应用层面使用的方式,也解决了一些实际的困难。而AIO是异步IO和前面的系列也有点关系
- java 多线程死锁 相信有过多线程编程经验的朋友,都吃过死锁的苦。除非你不使用多线程,否则死锁的可能性会一直存在。为什么会出现
- 一、前言Hello,又见面了,今天分享如何使用Unity制作计算器,难度中等,可以用来学习,或者当成其他项目的小组件导入。当然,也可以导出来
- 本文讲述在mybatis中如何使用ognl表达式实现动态组装sql语句新建Users实体类:public class Users { &nb
- Java栈之链式栈存储结构实现一、链栈采用单链表来保存栈中所有元素,这种链式结构的栈称为链栈。二、栈的链式存储结构实现package com
- java中字符串转整数及MyAtoi方法的实现 该题虽然和我们正常使
- import java.util.Arrays;/** * 栈的实现<br> * @author Skip&
- 1.下载JDK查看最新:http://www.oracle.com/technetwork/java/javase/downloads/in
- 调用示例: 执行效果: 2.实现代码:/// <summary> 2 &
- 前言在使用Java开发接口请求中,我们需要对请求进行进行统一返回值,这时候我们自己封装一个统一的Result返回类,下面就介绍下我用的这种的
- Android-webview和js互相调用Android 和 H5 都是移动开发应用的非常广泛。市面上很多App都是使用Android开发
- 在一般性开发中,笔者经常看到很多同学在对待java并发开发模型中只会使用一些基础的方法。比如Volatile,synchronized。像L
- 稳定度(稳定性)一个排序算法是稳定的,就是当有两个相等记录的关键字R和S,且在原本的列表中R出现在S之前,在排序过的列表中R也将会是在S之前
- 1: * 的定义: * 实际上是一个类,这个类实现了特定的接口,然后将这个类在 web.xml 文件中进行描述,这样服务器在启动的时候就可
- (Memory Leak,内存泄漏)为什么会产生内存泄漏?当一个对象已经不需要再使用本该被回收时,另外一个正在使用的对象持有它的引用从而导致
- 目标:list中有0到39共40个元素,删除其中索引是10、20、30的元素方案一:使用普通for循环从前往后遍历再删除//初始化List列
- 开篇本文主要来探讨一下 redis 的单线程模型,文章前半部分会先引用某网络课程讲解的内容(图片+语言描述),后半部分是本人粗略阅读 red
- class Dirctonary { &nbs
- mysql有个字段是bit,只存储1和0,是二进制存储,那么在java的dao层如何映射成boolean呢@Column(name=&quo
- 本文实例讲述了Java实现的日期处理类。分享给大家供大家参考,具体如下:开发中常常要使用日期,先小结如下,以备后用。import java.