java.util.concurrent包(5)——CountDownLatch使用

简介:
Java的concurrent包里面的CountDownLatch其实可以被看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。

可以向CountDownLatch对象设置一个初始的数字作为计数值,任何调用这个对象上的await()方法都会阻塞,直到这个计数器的计数值被其他的线程减为0为止。

CountDownLatch的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如这个想要继续往下执行的任务调用一个CountDownLatch对象的await()方法,其他的任务执行完自己的任务后调用同一个CountDownLatch对象上的countDown()方法,这个调用await()方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到0为止。

主要方法
public CountDownLatch(int count)
public void countDown()
public void await() throws InterruptedException
 
构造方法参数指定了计数的次数
countDown方法,当前线程调用此方法,则计数减一
await方法,调用此方法会一直阻塞当前线程,直到计时器的值为0

例如有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。

public class Boss implements Runnable
{
private final CountDownLatch countDownLatch;
private String name;

public Boss(String name, CountDownLatch countDownLatch)
{
super();
this.name = name;
this.countDownLatch = countDownLatch;
}

public void run()
{
try
{
System.out.println("老板" + name + "正在等工人把活干完");
countDownLatch.await();
System.out.println("工人活都干完了,老板开始检查了!");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

public class Worker implements Runnable
{
private final CountDownLatch countDownLatch;
private String name;

public Worker(String name, CountDownLatch countDownLatch)
{
super();
this.name = name;
this.countDownLatch = countDownLatch;
}

public void run()
{
doWork();
countDownLatch.countDown();
}

private void doWork()
{
try
{
System.out.println(this.name + "正在干活!");
long duration = new Random().nextInt(5) * 1000;
Thread.sleep(duration);
System.out.println(this.name + "活干完啦!");
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}

public class BossWorkerTest
{
public static void main(String[] args)
{
ExecutorService executor = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(3);

Worker w1 = new Worker("张一", countDownLatch);
Worker w2 = new Worker("张二", countDownLatch);
Worker w3 = new Worker("张三", countDownLatch);
Boss boss = new Boss("王老板", countDownLatch);
executor.execute(w3);
executor.execute(w2);
executor.execute(w1);
executor.execute(boss);
executor.shutdown();
}
}

下面是本地机器上运行的一次结果,每次运行的结果可能与下面不一样,但老板检查永远是在最后面。

张三正在干活!
张二正在干活!
张一正在干活!
张三活干完啦!
老板王老板正在等工人把活干完
张一活干完啦!
张二活干完啦!
工人活都干完了,老板开始检查了!

再看一个例子,该程序用来模拟发送命令与执行命令。
主线程代表教练,新建3个线程代表运动员。
运动员一直等待着教练下达命令。若教练没有下达命令,则运动员们都必须等待。
一旦命令下达,运动员们都去执行自己的任务,教练处于等待状态,运动员们任务执行完毕则报告给教练,教练则结束等待。

public class CountdownLatchTest
{
public static void main(String[] args)
{
// 创建一个线程池
ExecutorService service = Executors.newCachedThreadPool();
// 教练的命令设置为1,教练一下达命令,则countDown变为0,运动员们执行任务
final CountDownLatch cdOrder = new CountDownLatch(1);
// 因为有三个运动员故初始值为3,每个运动员执行任务完毕则countDown一次,当三个都执行完毕变为0则教练停止等待
final CountDownLatch cdAnswer = new CountDownLatch(3);
for (int i = 0; i < 3; i++)
{
Runnable runnable = new Runnable()
{
public void run()
{
try
{
System.out.println("线程" + Thread.currentThread().getName() + "正准备接受命令");
// 运动员们都处于等待命令状态
cdOrder.await();
System.out.println("线程" + Thread.currentThread().getName() + "已接受命令");
Thread.sleep((long) (Math.random() * 10000));
System.out.println("线程" + Thread.currentThread().getName() + "回应命令处理结果");
// 任务执行完毕返回给教练,cdAnswer减1
cdAnswer.countDown();
}
catch (Exception e)
{
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try
{
Thread.sleep((long) (Math.random() * 10000));

System.out.println("线程" + Thread.currentThread().getName() + "即将发布命令");
// 发送命令cdOrder减1,处于等待的运动员们停止等待转去执行任务
cdOrder.countDown();
System.out.println("线程" + Thread.currentThread().getName() + "已发送命令,正在等待结果");
// 命令发送后教练处于等待状态,一旦cdAnswer为0时停止等待继续往下执行
cdAnswer.await();
System.out.println("线程" + Thread.currentThread().getName() + "已收到所有响应结果");
}
catch (Exception e)
{
e.printStackTrace();
}
service.shutdown();
}
}

原帖地址:
http://zapldy.iteye.com/blog/746458
http://www.cnblogs.com/liuling/p/2013-8-20-02.html
目录
相关文章
|
3月前
|
安全 Java API
JAVA并发编程JUC包之CAS原理
在JDK 1.5之后,Java API引入了`java.util.concurrent`包(简称JUC包),提供了多种并发工具类,如原子类`AtomicXX`、线程池`Executors`、信号量`Semaphore`、阻塞队列等。这些工具类简化了并发编程的复杂度。原子类`Atomic`尤其重要,它提供了线程安全的变量更新方法,支持整型、长整型、布尔型、数组及对象属性的原子修改。结合`volatile`关键字,可以实现多线程环境下共享变量的安全修改。
|
4月前
|
安全 Java 调度
解锁Java并发编程高阶技能:深入剖析无锁CAS机制、揭秘魔法类Unsafe、精通原子包Atomic,打造高效并发应用
【8月更文挑战第4天】在Java并发编程中,无锁编程以高性能和低延迟应对高并发挑战。核心在于无锁CAS(Compare-And-Swap)机制,它基于硬件支持,确保原子性更新;Unsafe类提供底层内存操作,实现CAS;原子包java.util.concurrent.atomic封装了CAS操作,简化并发编程。通过`AtomicInteger`示例,展现了线程安全的自增操作,突显了这些技术在构建高效并发程序中的关键作用。
72 1
|
21天前
|
Java
Java之CountDownLatch原理浅析
本文介绍了Java并发工具类`CountDownLatch`的使用方法、原理及其与`Thread.join()`的区别。`CountDownLatch`通过构造函数接收一个整数参数作为计数器,调用`countDown`方法减少计数,`await`方法会阻塞当前线程,直到计数为零。文章还详细解析了其内部机制,包括初始化、`countDown`和`await`方法的工作原理,并给出了一个游戏加载场景的示例代码。
Java之CountDownLatch原理浅析
|
3月前
|
算法 Java
JAVA并发编程系列(8)CountDownLatch核心原理
面试中的编程题目“模拟拼团”,我们通过使用CountDownLatch来实现多线程条件下的拼团逻辑。此外,深入解析了CountDownLatch的核心原理及其内部实现机制,特别是`await()`方法的具体工作流程。通过详细分析源码与内部结构,帮助读者更好地理解并发编程的关键概念。
|
2月前
|
Java Apache Maven
Java/Spring项目的包开头为什么是com?
本文介绍了 Maven 项目的初始结构,并详细解释了 Java 包命名惯例中的域名反转规则。通过域名反转(如 `com.example`),可以确保包名的唯一性,避免命名冲突,提高代码的可读性和逻辑分层。文章还讨论了域名反转的好处,包括避免命名冲突、全球唯一性、提高代码可读性和逻辑分层。最后,作者提出了一个关于包名的问题,引发读者思考。
Java/Spring项目的包开头为什么是com?
|
3月前
|
Java API 数据处理
Java 包(package)的作用详解
在 Java 中,包(package)用于组织和管理类与接口,具有多项关键作用:1)系统化组织代码,便于理解和维护;2)提供命名空间,避免类名冲突;3)支持访问控制,如 public、protected、默认和 private,增强封装性;4)提升代码可维护性,实现模块化开发;5)简化导入机制,使代码更简洁;6)促进模块化编程,提高代码重用率;7)管理第三方库,避免命名冲突;8)支持 API 设计,便于功能调用;9)配合自动化构建工具,优化项目管理;10)促进团队协作,明确模块归属。合理运用包能显著提升代码质量和开发效率。
|
3月前
|
Java 数据安全/隐私保护
Java 包(package)的使用详解
Java中的包(`package`)用于组织类和接口,避免类名冲突并控制访问权限,提升代码的可维护性和可重用性。通过`package`关键字定义包,创建相应目录结构即可实现。包可通过`import`语句导入,支持导入具体类或整个包。Java提供多种访问权限修饰符(`public`、`protected`、`default`、`private`),以及丰富的标准库包(如`java.lang`、`java.util`等)。合理的包命名和使用对大型项目的开发至关重要。
|
4月前
|
Java Maven 数据库
|
4月前
|
JavaScript Java API
Java日志通关(二) - Slf4j+Logback 整合及排包
作者日常在与其他同学合作时,经常发现不合理的日志配置以及五花八门的日志记录方式,后续作者打算在团队内做一次Java日志的分享,本文是整理出的系列文章第二篇。
|
4月前
|
Java
Java应用结构规范问题之在biz层的convert包实现转换的问题如何解决
Java应用结构规范问题之在biz层的convert包实现转换的问题如何解决