少商剑:剑路雄劲,石破天惊,CountDownLatch以其强大而直接的方式控制线程等待和同步,具有明确而有力的作用。
CountDownLatch是一个同步工具类,它是根据计数器实现的,构造函数初始时会指定总的计数数量,每调用一次countDown数量会减一,当数量为0时,闸门将会放开,await等待的线程进而继续执行。
例如,小红,小兰,小明一起去野餐,他们约定先一起到公园门口再开始活动,这时计数器初始值就是3,当小红到达目的地,计数器减1,小红继续等待小兰和小明;小兰到达计数器再减1,再一起等待小明的到达;小明到达,数量就减为0了,他们三人再一起进行活动。
一个简单的动画流程:
组装加工一台电脑,我们需要加工CPU、主板、内存、显卡、电源等部件,如果进行串行加工,那需要等待CPU加工完再加工主板,那耗时就是所有部件的总和,但如果像工厂那样,流水线作业并行处理,加工CPU的同时,其他所有部件也一起加工,等待所有的部件加工完,我们再组装成电脑,这样效率快了很多。
public class CountDownLatchCase {
public static void produce() throws InterruptedException {
List<Task> taskList = new ArrayList<>();
taskList.add(new Task("CPU",2000L));
taskList.add(new Task("主板",800L));
taskList.add(new Task("内存",500L));
taskList.add(new Task("显卡",1000L));
taskList.add(new Task("电源",200L));
CountDownLatch latch = new CountDownLatch(taskList.size());
ConcurrentHashMap<String,String> results = new ConcurrentHashMap<>();
for (int i = 0; i < taskList.size(); i++) {
Task task = taskList.get(i);
new Thread(() -> {
try {
Thread.sleep(task.getTime());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
latch.countDown();
String threadName = Thread.currentThread().getName();
results.put(threadName,task.getName());
System.out.println(threadName+"完成加工:"+task.getName()+",耗时:"+task.getTime()+"毫秒");
},"线程"+(i+1)).start();
}
latch.await();
for (Map.Entry<String, String> entry : results.entrySet()) {
System.out.println("总部收到了来自"+entry.getKey()+"加工的"+entry.getValue());
}
if(results.size() == taskList.size()){
System.out.println("所有部件加工完成,可以组装电脑");
}
}
public static void main(String[] args) throws InterruptedException {
produce();
}
}
运行结果
线程5完成加工:电源,耗时:200毫秒
线程3完成加工:内存,耗时:500毫秒
线程2完成加工:主板,耗时:800毫秒
线程4完成加工:显卡,耗时:1000毫秒
线程1完成加工:CPU,耗时:2000毫秒
总部收到了来自线程4加工的显卡
总部收到了来自线程5加工的电源
总部收到了来自线程2加工的主板
总部收到了来自线程3加工的内存
总部收到了来自线程1加工的CPU
所有部件加工完成,可以组装电脑
CyclicBarrier直译过来就是“循环栅栏”,在Java并发编程中,用于控制一组线程,先到达栅栏的等待其他未到达的线程,都到达后再一起继续执行,跑到下一个栅栏,也是同样等待所有线程都到达栅栏再继续到下一个栅栏,以此往复。
CyclicBarrier内有一个计数器,在构造函数初始化时会初始计数器的值,当调用await方法时,计数器会减一并将当前线程进行阻塞,表示次线程已到达栅栏,等待其他线程全部执行完毕,也就是计数器的值为0时。
在我们玩一些PVP游戏中,我们一般在开始游戏的时候都会有 匹配玩家->选择角色->加载进入 的过程,而每一个流程都需要所有玩家全部确认完成后才能继续,每一个过程相当于就是一个栅栏,拦住全部的玩家,直到所有的玩家完成才进行下一步。
比较经典的一张图:
代码实现:
public class CyclicBarrierCase {
//匹配游戏->选择角色->加载进入
public static void produce(){
List<String> player = new ArrayList<>();
player.add("玩家一");
player.add("玩家二");
player.add("玩家三");
player.add("玩家四");
CyclicBarrier barrier = new CyclicBarrier(player.size());
for (int i = 0; i < player.size(); i++) {
new Thread(() -> {
String threadName = Thread.currentThread().getName();
sleep(); //准备游戏时间
System.out.println(threadName+"准备完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
sleep(); //选择角色时间
System.out.println(threadName + "选择完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
sleep(); //加载进入时间
System.out.println(threadName + "加载完成");
try {
barrier.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (BrokenBarrierException e) {
throw new RuntimeException(e);
}
},player.get(i)+"线程").start();
}
}
public static void sleep(){
//睡1到3秒的随机数
int random = RandomUtil.randomInt(1000, 3000);
try {
Thread.sleep(random);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
produce();
}
}
执行结果:
调用await时,会让我们捕获两个异常,一个是BrokenBarrierException另一个是InterruptedException。
出现BrokenBarrierException这种情况可能是CyclicBarrier的某个线程在等待期间被中断,或者CyclicBarrier被重置。这通常意味着CyclicBarrier无法正常工作,处理的方案可能有捕获异常并记录日志,通过某种共享状态或消息机制通知其他线程CyclicBarrier已损坏,执行一些恢复操作来尽量弥补或减轻由于栅栏损坏导致的影响。
出现InterruptedException是因为在调用await(long timeout, TimeUnit unit)我们指定了超时时间,当指定时间过后,线程还没有被放行,抛出超时异常,处理超时的策略可能包括重试机制或者回退逻辑。但重要的是要确保所有的线程在超时后都能正确地处理这种情况,避免资源泄漏或者线程阻塞。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。