Java并发API提供CountDownLatch类,允许一个或多个线程等待直到一组操作形成。此类初始化时包含一个整型数字,用来记录线程将要等待的操作数量。当线程想要等待这些操作的执行时,使用await()方法,设置线程进入休眠直到操作完成。当其中一个操作完成时,使用countDown()方法减少CountDownLatch类的内置计数器值。当计数器值降到0时,类唤醒所有在await()方法中休眠的线程。
在本节中,学习使用CountDownLatch类实现视频会议系统。此系统需要等待所有参会者到场方可开始。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤完成范例:
-
创建名为Videoconference的类,实现Runnable接口。这个类将实现视频会议系统:
public class Videoconference implements Runnable{ -
定义名为controller的CountDownLatch对象:
private final CountDownLatch controller; -
实现类构造函数,初始化CountDownLatch属性。Videoconference类接收参会者数量为参数,等待人员到场:
public Videoconference(int number) { controller = new CountDownLatch(number); } -
实现arrive()方法,每当一个参会人员到场时调用此方法。接收参数时名为name的字符串类型:
public void arrive(String name) { -
首先,包含接收的参数信息输出到控制台中:
System.out.printf("%s has arrived.\n", name); -
然后,调用CountDownLatch对象的countDown()方法:
controller.countDown(); -
最后,输出未到场参会人员的数量信息到控制台中,使用CountDownLatch对象的getCount()方法:
System.out.printf("VideoConference: Wait for %d participants.\n", controller.getCount()); -
接下来,实现视频会议系统的主方法,Runnable接口中必须有的run()方法:
@Override public void run() { -
首先,使用getCount()方法输出计划参加视频会议的人数信息到控制台中:
System.out.printf("VideoConference : Initialization : %d participants.\n", controller.getCount()); -
然后,使用await()方法等待所有参会者,因为此方法会抛出InterruptedException异常,需要代码进行处理:
try { controller.await(); -
最后,输出信息表明所有的参会者已到达:
System.out.printf("VideoConference : All the participants have come.\n"); System.out.printf("VideoConference : Let's start...\n"); } catch (InterruptedException e) { e.printStackTrace(); } -
然后,创建Participant类实现其Runnable接口,此类代表视频会议中的每个参会者:
public class Participant implements Runnable{ -
定义名为conference的私有Videoconference属性:
private Videoconference conference; -
定义名为name的私有字符串属性:
private String name; -
实现类构造函数,初始化这两个属性:
public Participant(Videoconference conference, String name){ this.conference = conference; this.name = name; } -
实现参会者的run()方法:
@Override public void run() { -
首先,是指线程随机休眠一段时间:
long duration = (long) (Math.random() * 10); try { TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } -
然后,使用Videoconference属性的arrive()方法指明此参会者到达:
conference.arrive(name); -
最后,实现范例的主类,创建一个包含main()方法的Main类:
public class Main { public static void main(String[] args) { -
接下来,创建名为conference的Videoconference属性,等待10位参会者:
Videoconference conference = new Videoconference(10); -
创建Videoconference属性的线程,开始执行:
Thread threadConference = new Thread(conference); threadConference.start(); -
创建10个Participant对象,用Thread对象分别运行它们,然后启动所有线程:
for(int i = 0 ; i < 10 ; i ++) { Participant p = new Participant(conference, "Participant" + i); Thread t = new Thread(p); t.start(); }
工作原理
CountDownLatch类包括三个基础要素:
- 初始值:确定CountDownLatch对象等待多少事件
- await()方法,被等待所有事件结束的线程调用
- countDown()方法,在事件结束执行的时候,被事件调用
当创建CountDownLatch对象时,它使用构造函数的参数初始化一个内置计数器。每次线程调用countDown()方法,CountDownLatch对象的内置计数器值减1。当计数器值等于0时,CountDownLatch对象唤醒所有等待await()方法的线程。
没有方法能够再次初始化或更改CountDownLatch对象的内置计数器值。一旦计数器被初始化,唯一能够修改其值的方法就是之前讲解的countDown()方法。当值等于0时,await()方法的所有调用立即返回,所有后续的countDown()调用均失效。
但是,与其它同步方法有一些不同,如下所示:
- CountDownLatch机制不保护共享资源或者临界区。它用来同步需要执行各种任务的一个或多个线程。
- 它只确认一点,如前所述,一旦CountDownLatch的计数器值为0,所有的方法调用均失效。如果想再次进行相同的同步操作,只能创建一个新的对象。
下图显示本范例在控制台输出的执行信息:
可以看到参会者如何到达,并且一旦内置计数器值为0,CountDownLatch对象唤醒Videoconference对象,输出表明视频会议可以开始的信息。
扩展学习
以下是CountDownLatch类中await()方法的另一种形式:
- await(long time, TimeUnit unit):在这个方法中,线程将持续休眠直到它被中断, 也就是说,或者CountDownLatch的内置计数器值变成0,或者已过指定的时间。TimeUnit是一个枚举类型的类,包含如下常量:DAYS、HOURS、MICROSECONDS、MILLISECONDS、MINUTES、NANOSECONDS、和SECONDS。