java并发线程同步器CountDownLatch
CountDownLatch
我们在多线程编程时常常会遇到,主线程中启动多个子线程执行任务,并且主线程需要等待所有的子线程执行完成后在返回。这时我们可以使用CountDownLatch类来完成。
代码示例:
public static void countDownLatch(){
CountDownLatch latch = new CountDownLatch(10);
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
Thread thread = new Thread(new Runnable(){
@Override
public void run() {
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程在执行:"+Thread.currentThread().getName());
latch.countDown();
}
});
thread.setName("thread_"+i);
threads[i] = thread;
}
System.out.println("开始启动所有的线程。。。。。。。。。。。");
Arrays.stream(threads).forEach(Thread::start);
System.out.println("线程启动完成:");
try {
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程返回的结果。。。。。。。。。。");
}
执行结果:
解读:
上述代码中定义了一个长度为10的线程数组,创建10个子线程,然后分别将其启动。需要注意的是我们创建了CountDownLatch类来用作等这10个子线程全部执行完成后在将主线成中的打印输出在控制台。
我们在创建CountDownLatch时指定了其内部计数器的值为10,每一次一个子线程执行完自己的任务都会调用CountDownLatch.countDown()方法来讲计数器的值减1。我们在主线程中调用CountDownLatch.await()方法,该方法调用后会阻塞当前线程。直到CountDownLatch中内部计数器的值为0,或者其他子线程调用interrupt()方法中断了当前线程。才会停止阻塞。
CountDownLatch内部是使用AQS来实现计数操作的。
CountDownLatch.countDown()调用后,计数器的值递减,递减后如果计数器为0,则唤醒所有因调用await方法被阻塞的线程,否则什么都不做。countDown方法内部是使用CAS来递减计数器的。
CountDownLatch.await()被调用后将会阻塞当前线程,直到内部计数器为0或者其他线程调用interrupt方法中断当前线程,才会停止阻塞。
CountDownLatch.await(long timeout,TimeUnit unit)与CountDownLatch.await()类似只不过多增加了超时时间,当到达了超时时间会直接返回false当然也停止阻塞。
使用CountDownLatch.countDown()的好处是我们可以在子线程运行的任何时候调用。也就是未必我们要等到子线程全部执行完后返回,可能在某个关键的节点下去调用,也可以返回。
但是一旦CountDownLatch中的计数器为0时,在调用CountDownLatch.await()方法就不会在阻塞了。
最测试代码:
public static void countDownLatch1(){
CountDownLatch latch = new CountDownLatch(12);
Thread[] threads = new Thread[12];
for (int i = 0; i < threads.length; i++) {
Thread thread = new Thread(()->{
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前线程在执行:"+Thread.currentThread().getName());
latch.countDown();
});
thread.setName("thread_"+i);
threads[i] = thread;
}
Thread thread1 = new Thread(()->{
try {
latch.countDown();
System.out.println("进行测试线程阻塞1。。。。。。");
latch.await();
System.out.println("阻塞停止1。。。。。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
);
thread1.setName("test_thread_1");
Thread thread2 = new Thread(()->{
try {
latch.countDown();
System.out.println("进行测试线程阻塞2。。。。。。");
latch.await();
System.out.println("阻塞停止2。。。。。。。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread2.setName("test_thread_2");
System.out.println("开始启动所有的线程。。。。。。。。。。。");
threads[10] = thread1;
threads[11] = thread2;
Arrays.stream(threads).forEach(Thread::start);
System.out.println("线程启动完成:");
try {
latch.await();
Thread.currentThread().sleep(1000);
System.out.println("主线程返回的结果。。。。。。。。。。");
latch.await();
System.out.println("主线程返回的结果。。。。不等待直接返回。。。。");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果:
程序输出结果与我们的描述相同:
1,所有调用CountDownLatch.await()的线程都会被阻塞,也都会被同时释放
2,一旦CountDownLatch中的计数器为0时,在调用CountDownLatch.await()方法就不会在阻塞了