概述:
`CountDownLatch`是Java并发包中的一个同步工具类,用于控制线程的执行顺序和协调多个线程之间的操作。它通过一个计数器来实现,计数器的初始值由用户指定,每当一个线程完成了特定任务或达到了某个状态,计数器的值就会减少。当计数器的值变为0时,等待`CountDownLatch`的线程将被释放。
应用场景:
`CountDownLatch`常用于以下场景:
1. 主线程等待多个子线程完成任务后再继续执行。
2. 多个线程等待某个共享资源就绪后再同时开始执行。
3. 控制多个线程按照特定的顺序执行。
优点:
1. 简单易用:`CountDownLatch`提供了简单的接口,易于使用和理解。
2. 灵活性:可以根据实际需求设置等待的线程数量,可以灵活地控制线程的执行顺序和协调多个线程之间的操作。
缺点:
1. 一次性:`CountDownLatch`的计数器只能减少到0,一旦减少到0后,无法重置计数器,因此只能使用一次。
2. 无法逆转:一旦计数器的值减少到0,等待线程将被释放,无法再次等待。
主要方法:
1. `CountDownLatch(int count)`:
构造函数,用于创建一个`CountDownLatch`对象,并指定需要等待的线程数量。
2. `void await()`:
使当前线程等待,直到`count`值减少到0。它会阻塞当前线程,直到所有线程都调用了`countDown()`方法,如果`count`值已经为0,则该方法立即返回。
3. `boolean await(long timeout, TimeUnit unit)`:
方法与`await()`方法类似,但它还接受一个超时时间参数。使当前线程等待,直到`count`值减少到0或超过指定的超时时间。如果在超时时间内`count`值变为0,则返回`true`;如果超过超时时间仍未达到0,则返回`false`。
4. `void countDown()`:
将`count`值减少1。当某个线程完成了特定任务或达到了某个状态时,应调用此方法来减少`count`值。
案例:
1、猴哥吃蕉====await()
import java.util.concurrent.CountDownLatch; /** * 猴哥 */ public class MonkeyBanana extends Thread { CountDownLatch countDownLatch; String name; public MonkeyBanana(String name, CountDownLatch countDownLatch) { this.name = name; this.countDownLatch = countDownLatch; } @Override public void run() { System.out.println(name + "开始吃香蕉"); try { long l = (long) (Math.random() * 10000); Thread.sleep(l); if (l / 1000 == 0) { System.out.println(name + "简直不要太厉害了,跟我们表演了一波一口闷香蕉!"); } else if (l / 1000 > 0 && l / 1000 < 5) { System.out.println(name + "在" + l / 1000 + "秒内吃完了香蕉!"); } else if (l / 1000 == 5) { System.out.println(name + "在最后1秒内吃完了香蕉!"); } else if (l / 1000 > 5) { System.out.println(name + "在" + l / 1000 + "秒内吃完了香蕉,已超时!"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { countDownLatch.countDown();//线程倒数器-每次都会减1 } } }
import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; public class CountDownLatchTest { public static void main(String[] args) { monkeyEatAwait(); } /** * 猴哥吃香蕉 */ public static void monkeyEatAwait() { try { //倒数器记数 CountDownLatch downLatch = new CountDownLatch(1); System.out.println("猴哥吃香蕉大赛预备::"); //倒数3个数 for (int i = 3; i > 0; i--) { System.out.println("倒数 : " + i); TimeUnit.SECONDS.sleep(1); } downLatch.countDown(); downLatch.await(); System.out.println("BOOM 开始!"); //几个猴哥再比赛 int monkeyNum = 5;//需要等待的线程数量。也就是说,这些线程执行完之后才会往下继续走 CountDownLatch countDownLatch = new CountDownLatch(monkeyNum); for (int i = 0; i < monkeyNum; i++) { new MonkeyBanana("猴哥" + (i + 1), countDownLatch).start(); } //等待所有猴哥都吃完香蕉 countDownLatch.await(); System.out.println("所有猴哥都吃完了香蕉!"); } catch (Exception e) { e.printStackTrace(); } } }
执行结果:
分析:
这个是等待所有猴哥都吃完香蕉。也就是所有的子线程任务都走完之后才会走主线程,执行主线程方法。
2、猴哥吃蕉====await(5, TimeUnit.SECONDS);
import java.util.Date; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.ReentrantLock; public class CountDownLatchTest { public static void main(String[] args) { monkeyEatAwaitPar(); } /** * 猴哥吃香蕉 */ public static void monkeyEatAwaitPar() { try { //倒数器记数 CountDownLatch downLatch = new CountDownLatch(1); System.out.println("猴哥吃香蕉大赛预备::"); //倒数3个数 for (int i = 3; i > 0; i--) { System.out.println("倒数 : " + i); TimeUnit.SECONDS.sleep(1); } downLatch.countDown(); downLatch.await(); System.out.println("BOOM 开始!"); //几个猴哥再比赛 int monkeyNum = 5;//需要等待的线程数量。也就是说,这些线程执行完之后才会往下继续走 CountDownLatch countDownLatch = new CountDownLatch(monkeyNum); for (int i = 0; i < monkeyNum; i++) { new MonkeyBanana("猴哥" + (i + 1), countDownLatch).start(); } //超时判断 boolean await = countDownLatch.await(5, TimeUnit.SECONDS); //超过5秒结束比赛 if (!await) { System.out.println("时间到!"); } else { System.out.println("所有猴哥都吃完了香蕉!"); } } catch (Exception e) { e.printStackTrace(); } } }
执行结果
分析:
这里我们用了带入参的await方法,判断如果吃香蕉时间超出5秒钟,则算是失败!可以处理特殊情况下线程卡死,超时等状态。
总结:
`CountDownLatch`是一个非常有用的同步工具,适用于需要等待多个线程完成任务或达到某个状态后再继续执行的场景。它简单易用,但只能使用一次且无法逆转。在合适的场景下,使用`CountDownLatch`可以有效地实现线程之间的同步和协调。