写点什么

Java 并发基础(二):主线程等待子线程结束

用户头像
看山
关注
发布于: 2021 年 04 月 05 日
Java 并发基础(二):主线程等待子线程结束

你好,我是看山。


在很多时候,我们期望实现这么一种功能:在主线程中启动一些子线程,等待所有子线程执行结束后,主线程再继续执行。比如:老板分配任务,众多工人开始工作,等所有工人完成工作后,老板进行检查。


解决方法分析:


  1. 主线程通过 join 等待所有子线程完成后,继续执行;

  2. 主线程知道子线程的数量、未完成子线程数量,主线程等待所有子线程完成后,才继续执行。

通过 join 实现

第一种方式,可以直接调用 Java API 中关于线程的 join 方法等待该线程终止,可以直接实现。


每个工人都是一个线程,其 run 方法是工作的实现,每个工人通过名字进行区分,具体代码如下:


import java.util.Random;import java.util.concurrent.TimeUnit;
public class Worker extends Thread { private String workerName;
public Worker(String workerName) { this.workerName = workerName; }
/** * @see java.lang.Thread#run() */ @Override public void run() { System.out.println(this.workerName + "正在干活。.."); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException e) { } System.out.println(this.workerName + "活干完了!"); } public String getWorkerName() { return workerName; }}
复制代码


老板招收工人,然后安排工人工作,之后等待工人工作完,检查工人的工作成果(资本家啊。),具体代码如下:


import java.util.List;
public class Boss { private List<Worker> workers;
public Boss(List<Worker> workers) { System.out.println("老板招收工人。"); this.workers = workers; }
public void work() { System.out.println("老板开始安排工人工作。.."); for (Worker worker : workers) { System.out.println("老板安排" + worker.getWorkerName() + "的工作"); worker.start(); } System.out.println("老板安排工作结束。..");
System.out.println("老板正在等所有的工人干完活。....."); for (Worker w : workers) { try { w.join(); } catch (InterruptedException e) { } } System.out.println("工人活都干完了,老板开始检查了!"); }}
复制代码


现在写 main 方法进行测试:


import java.util.ArrayList;import java.util.List;
public class JoinDemo { public static void main(String[] args) { Worker w1 = new Worker("张三"); Worker w2 = new Worker("李四"); Worker w3 = new Worker("王五"); List<Worker> workers = new ArrayList<Worker>(); workers.add(w1); workers.add(w2); workers.add(w3); Boss boss = new Boss(workers); boss.work(); System.out.println("main 方法结束"); }}
复制代码


执行结果为:


老板招收工人。老板开始安排工人工作。..老板安排张三的工作老板安排李四的工作张三正在干活。..老板安排王五的工作李四正在干活。..老板安排工作结束。..老板正在等所有的工人干完活。.....王五正在干活。..王五活干完了!张三活干完了!李四活干完了!工人活都干完了,老板开始检查了!main 方法结束
复制代码

通过 CountDownLatch 实现

第二种方式可以自己实现一种计数器,用于统计子线程总数、未完成线程数,当未完成线程数大约 0,主线程等待;当未完成线程数等于 0,主线程继续执行。


当然,既然我们现在想到这种方式,Java API 的团队当然也会想到,JDK 1.5 提供了 CountDownLatch 用于实现上述方法。


于是对上述的工人方法进行修改:


import java.util.Random;import java.util.concurrent.CountDownLatch;import java.util.concurrent.TimeUnit;
public class Worker extends Thread { private CountDownLatch downLatch; private String workerName;
public Worker(CountDownLatch downLatch, String workerName) { this.downLatch = downLatch; this.workerName = workerName; }
public void run() { System.out.println(this.workerName + "正在干活。.."); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException ie) { } System.out.println(this.workerName + "活干完了!"); this.downLatch.countDown(); } public String getWorkerName() { return workerName; }}
复制代码


latch.countDown(),是用于在子线程执行结束后计数器减一,即未完成子线程数减一。


老板类也得做出相应的修改:


import java.util.List;import java.util.concurrent.CountDownLatch;
public class Boss { private List<Worker> workers; private CountDownLatch downLatch;
public Boss(List<Worker> workers, CountDownLatch downLatch) { this.workers = workers; this.downLatch = downLatch; }
public void work() { System.out.println("老板开始安排工人工作。.."); for (Worker worker : workers) { System.out.println("老板安排" + worker.getWorkerName() + "的工作"); worker.start(); } System.out.println("老板安排工作结束。.."); System.out.println("老板正在等所有的工人干完活。....."); try { this.downLatch.await(); } catch (InterruptedException e) { } System.out.println("工人活都干完了,老板开始检查了!"); }
}
复制代码


latch.await(),是等待子线程结束。


编写 main 方法进行验证:


import java.util.ArrayList;import java.util.List;import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo { public static void main(String[] args) { CountDownLatch latch = new CountDownLatch(3);
Worker w1 = new Worker(latch, "张三"); Worker w2 = new Worker(latch, "李四"); Worker w3 = new Worker(latch, "王五"); List<Worker> workers = new ArrayList<Worker>(); workers.add(w1); workers.add(w2); workers.add(w3);
Boss boss = new Boss(workers, latch); boss.work(); System.out.println("main 方法结束"); }}
复制代码


执行结果为:


    老板开始安排工人工作。..    老板安排张三的工作    老板安排李四的工作    张三正在干活。..    老板安排王五的工作    李四正在干活。..    老板安排工作结束。..    老板正在等所有的工人干完活。.....    王五正在干活。..    王五活干完了!    李四活干完了!    张三活干完了!    工人活都干完了,老板开始检查了!    main 方法结束
复制代码

使用循环栅栏 CyclicBarrier 实现

还有一种实现,这种方式不会阻塞主线程,但是会监听所有子线程结束。此处在上述的工人老板的场景中使用的话,代码如下:


工人类:


import java.util.Random;import java.util.concurrent.BrokenBarrierException;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.TimeUnit;
public class Worker extends Thread { private String workerName; private CyclicBarrier barrier;
public Worker(String workerName, CyclicBarrier barrier) { this.workerName = workerName; this.barrier = barrier; }
@Override public void run() { System.out.println(this.workerName + "正在干活。.."); try { TimeUnit.SECONDS.sleep(new Random().nextInt(10)); } catch (InterruptedException e) { } System.out.println(this.workerName + "活干完了!");
try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }
public String getWorkerName() { return workerName; }}
复制代码


老板类:


import java.util.List;
public class Boss { private List<Worker> workers;
public Boss(List<Worker> workers) { this.workers = workers; }
public void work() { System.out.println("老板开始安排工人工作。.."); for (Worker worker : workers) { System.out.println("老板安排" + worker.getWorkerName() + "的工作"); worker.start(); } System.out.println("老板安排工作结束。.."); System.out.println("老板正在等所有的工人干完活。....."); }
}
复制代码


main 方法测试:


import java.util.ArrayList;import java.util.List;import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier barrier = new CyclicBarrier(3, new Runnable() { @Override public void run() { System.out.println("工人活都干完了,老板开始检查了!"); } }); Worker w1 = new Worker("张三", barrier); Worker w2 = new Worker("李四", barrier); Worker w3 = new Worker("王五", barrier);
List<Worker> workers = new ArrayList<Worker>(); workers.add(w1); workers.add(w2); workers.add(w3); Boss boss = new Boss(workers); boss.work();
System.out.println("main 方法结束"); }}
复制代码


执行结果为:


老板开始安排工人工作。..老板安排张三的工作老板安排李四的工作张三正在干活。..老板安排王五的工作李四正在干活。..老板安排工作结束。..老板正在等所有的工人干完活。.....王五正在干活。..main 方法结束李四活干完了!王五活干完了!张三活干完了!工人活都干完了,老板开始检查了!
复制代码


通过结果分析可以很清楚的看出,boss 对象的 work 方法执行结束后,main 方法即开始执行。(此处“老板正在等所有的工人干完活。.....”打印之后是“王五正在干活。..”,然后才是“main 方法结束”,是因为对于 cpu 的抢占,甚至有一定的概率是“main 方法结束”会在最后打印。)


假设有这么一个功能,我们需要向数据库批量写入一些记录,然后记录这个操作使用的时间,但是我们又不想影响其他操作(即不想阻塞主线程),这个时候 CyclicBarrier 就派上用场了。




你好,我是看山,公众号:看山的小屋,10 年老猿,Apache Storm、WxJava、Cynomys 开源贡献者。游于码界,戏享人生。


个人主页:https://www.howardliu.cn

个人博文:主线程等待子线程结束



发布于: 2021 年 04 月 05 日阅读数: 12
用户头像

看山

关注

公众号「看山的小屋」 2017.10.26 加入

游于码界,戏享人生。 未来不迎,当时不杂,既过不恋。

评论

发布
暂无评论
Java 并发基础(二):主线程等待子线程结束