CountDownLatch是JDK1.5位于java.util.concurrent包下的一个同步辅助类,利用它利用实现类似计数器的功能。比如有一个任务A,它要等待其他所有任务执行完毕之后才能执行,这时我们就可以使用CountDownLatch来实现这种功能了。
CountDownLatch只有一个带参的构造器CountDownLatch(int count),其中参数count表示计数器的值。
在它的所有方法中,await()和countDown()是最为重要的。其中await()可以使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。而每次调用countDown()方法时就会递减锁存器的计数,如果计数到达零,则所有等待的线程会被释放。
下面我们通过例子来体会CountDownLatch的用法
问题:假设有8名考生要参加某一学科的考试,在教室里有一个监考老师,只有监考老师准备好后才能发试卷,然后当所有考生都完成考试之后监考老师才能收试卷离开考场。
对于上面问题,我们可以创建两个CountDownLatch,参数count值分别为1和8,表示监考老师和8名考生。然后启动8个线程(8名学生),每个线程执行前都先调用监考老师CountDownLatch的await()等待,直到监考老师发出开始考试命令后再开始考试(等待的线程被唤醒执行);同理,监考老师在所有考生都考完之前也一直等待(调用考生CountDownLatch的await()方法),每个考生考完后都调用countDown()使计数被减一,当最后一个考生考完之后计数达到零,等待着的监考老师被唤醒继续往下执行(收试卷)。
基于上面的分析,代码如下:
package com.gk.thread.latch;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class Exam {
public static void main(String[] args) throws InterruptedException {
final int NUMBER = 8; // 有8名考生
CountDownLatch teacher = new CountDownLatch(1);
CountDownLatch student = new CountDownLatch(NUMBER);
Runnable er = new ExamRunnable(teacher, student);
for (int i=1; i<=NUMBER; i++) {
String name = " student--" + i + " ";
new Thread(er, name).start();
}
/*
* 模拟监考老师等待1分钟之后发试卷,
* 在这之前所有的学生线程都必须等待
*/
Thread.sleep(1 * 1000 * 60);
System.out.println("=============" +
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + "开始考试" +
"===============");
System.out.println();
/*
* 由于teacher的计数器值为1,所以调用此方法后计数为零
* 所有由于调用teacher的await()方法而阻塞等待的线程都将被唤醒
*/
teacher.countDown();
student.await(); // 在student的计数为零之前阻塞监考老师线程(在这里为主线程)
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
"所有考试已考试完毕,监考老师可以收试卷了...");
}
}
package com.gk.thread.latch;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class ExamRunnable implements Runnable {
private CountDownLatch teacher;
private CountDownLatch student;
public ExamRunnable(CountDownLatch teacher, CountDownLatch student) {
this.teacher = teacher;
this.student = student;
}
@Override
public void run() {
try {
Thread.sleep((long)(Math.random() * 1000) * 60); // 模拟考生陆续到达考场
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
Thread.currentThread().getName() + "已经准备好,等待考试...");
System.out.println();
try {
teacher.await(); // 在teacher的计数为零之前阻塞所有学生线程
Thread.sleep((long)(Math.random() * 1000) * 60 * 5); // 模拟考生答题时间
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) +
Thread.currentThread().getName() + "已经考试完毕,离开考场...");
System.out.println();
/*
* 每个考生考完之后都调用countDown()使计数减一,
* 当最后一个考生考完之后计数为零,监考老师线程(主线程)被释放
*/
student.countDown();
}
}
上面示例使用的是await()方法,它还有另一重载方法await(long timeout, TimeUnit unit)。两者的不同之处在于前者的返回值为void,后者的返回值为boolean(当返回值为true时表明当前的计数器为零,如果在计数到达零之前超过了等待时间则返回false);前面说过调用countDown()方法使计数到达零可使由于调用await()方法而处于阻塞状态的线程被唤醒,所以两者的另一个不同之处在于后者可以设置阻塞时间,即当超出指定的等待时间后,由于调用await方法而处于阻塞状态线程也会被唤醒。
package com.gk.thread.latch;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class TimeDemo {
public static void main(String[] args) {
CountDownLatch cdl = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
boolean bool;
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 开始等待...");
try {
bool = cdl.await(2L, TimeUnit.SECONDS);
//cdl.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 等待时间超过2秒,不和你们玩了...");
System.out.println("boolean : " + bool);
}
}.start();
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1 * 1000 * 5); // 等待5秒之后再释放
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + " 等待时间结束啦...");
cdl.countDown();
}
}.start();
}
}
上面示例创建了两个线程,一个调用CountDownLatch的await方法等待2秒钟,另一个线程则等待5秒之后调用CountDownLatch的countDown()方法释放由于调用了await方法而等待的线程。不过由于await的时间只有2秒,所以没有等到5秒之后就已经“醒”了。