本文主要围绕一个问题展开:线程执行顺序,比如某个线程在其他线程并发执行完毕后最后执行,这里分别用 CountDownLatch、CyclicBarrier 、join()、线程池来实现。
CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier),又叫同步屏障。它可以让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。线程到达屏障的控制通过CyclicBarrier 的 await() 方法实现。
CyclicBarrier 的构造方法有 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier 还提供一个构造函数 CyclicBarrier(int parties, Runnable barrierAction) ,用于在线程都到达屏障时,优先执行barrierAction 这个 Runnable 对象,然后都到达屏障的线程继续执行。
在 CyclicBarrier 的内部定义了一个 ReentrantLock 对象,每当一个线程调用 CyclicBarrier 的 await 方法时,将剩余拦截的线程数减1,然后判断剩余拦截数是否为0,如果不是,进入 Lock 对象的条件队列等待。如果是则执行 barrierAction 对象的 run 方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁,接着先从 await 方法返回,再从 CyclicBarrier 的 await 方法中返回。
CountDownLatch 的作用是允许1或N个线程等待其他线程完成执行;而 CyclicBarrier 则是允许N个线程相互等待。
CountDownLatch 的计数器无法被重置;CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。
public class CyclicBarrierPractice {
static class Worker implements Runnable{
private String name;
private CyclicBarrier cyclicBarrier;
public Worker(String name, CyclicBarrier cyclicBarrier){
this.name = name;
this.cyclicBarrier = cyclicBarrier;
}
public void run(){
System.out.println(name + " is working");
try {
Thread.sleep(2000);
//到达屏障出(同步点)
cyclicBarrier.await();
//线程都到了后继续向下执行,也可以不要下面代码,什么都不做了
System.out.println(name + " do other things");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
static class Boss implements Runnable{
private String name;
public Boss(String name){
this.name = name;
}
public void run(){
System.out.println(name + " checks work");
}
}
public static void main(String[] args){
//其他线程都达到屏障后,再执行 boss 线程
CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Boss("boss"));
for(int i=0; i<3; i++){
new Thread(new Worker("worker"+i, cyclicBarrier)).start();
}
}
}
worker0 is working
worker1 is working
worker2 is working
boss checks work
worker2 do other things
worker0 do other things
worker1 do other things
join() 是 Thread 类的一个方法,join() 方法的作用是等待当前线程结束,也即让“主线程”等待“子线程”结束之后才能继续运行。t.join() 方法阻塞调用此方法的线程 (calling thread),直到线程 t完成,此线程再继续(看起来和同步调用类似);通常用于在 main 主线程内,等待其它线程完成后再继续执行 main 主线程。
Join 方法实现是通过 wait(Object 提供的方法)。 看源代码知会进入 while(isAlive()) 循环;即只要子线程是活的,主线程就不停的等待。
用 join 方式实现问题如下,在代码中 main 线程被阻塞直到 thread1,thread2,thread3 执行完,主线程才会顺序的执行thread4.
public class JoinPractice {
static class Worker implements Runnable {
private String name;
public Worker(String name){
this.name = name;
}
@Override
public void run(){
System.out.println(name + " is working");
}
}
static class Boss implements Runnable{
private String name;
public Boss(String name){
this.name = name;
}
@Override
public void run(){
System.out.println("boss checks work");
}
}
public static void main(String[] args){
Worker worker1 = new Worker("worker1");
Worker worker2 = new Worker("worker2");
Worker worker3 = new Worker("worker3");
Boss boss = new Boss("boss");
Thread thread1 = new Thread(worker1);
Thread thread2 = new Thread(worker2);
Thread thread3 = new Thread(worker3);
Thread thread4 = new Thread(boss);
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
thread4.start();
}
}
worker2 is working
worker1 is working
worker3 is working
boss checks work
Java 的 util.concurrent 包里面的 CountDownLatch 其实可以把它看作一个计数器(倒计时锁),只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是同时只能有一个线程去减这个计数器里面的值。
你可以向 CountDownLatch 对象设置一个初始的数字作为计数值,任何调用这个对象上的 await() 方法都会阻塞,直到这个计数器的计数值被其他的线程减为 0 为止。
CountDownLatch 的一个非常典型的应用场景是:有一个任务想要往下执行,但必须要等到其他的任务执行完毕后才可以继续往下执行。假如我们这个想要继续往下执行的任务调用一个 CountDownLatch 对象的 await() 方法,其他的任务执行完自己的任务后调用同一个CountDownLatch 对象上的 countDown() 方法,这个调用 await() 方法的任务将一直阻塞等待,直到这个CountDownLatch对象的计数值减到 0 为止。
举个例子,有三个工人在为老板干活,这个老板有一个习惯,就是当三个工人把一天的活都干完了的时候,他就来检查所有工人所干的活。记住这个条件:三个工人先全部干完活,老板才检查。所以在这里用 Java 代码设计两个类,Worker 代表工人,Boss 代表老板,代码使用了内部类实现。
public class OrderThreadExecute {
class Worker implements Runnable {
private CountDownLatch downLatch;
private String name;
public Worker(CountDownLatch downLatch, String name) {
this.downLatch = downLatch;
this.name = name;
}
@Override
public void run() {
this.doWork();
try {
TimeUnit.SECONDS.sleep(new Random().nextInt(10));
} catch (InterruptedException ie) {
}
System.out.println(this.name + "活干完了!");
this.downLatch.countDown();
}
private void doWork() {
System.out.println(this.name + "正在干活...");
}
}
class Boss implements Runnable {
private CountDownLatch downLatch;
public Boss(CountDownLatch downLatch) {
this.downLatch = downLatch;
}
@Override
public void run() {
System.out.println("老板正在等所有的工人干完活......");
try {
this.downLatch.await();
} catch (InterruptedException e) {
}
System.out.println("工人活都干完了,老板开始检查了!");
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newCachedThreadPool();
CountDownLatch latch = new CountDownLatch(3);
OrderThreadExecute orderThread = new OrderThreadExecute();
Worker w1 = orderThread.new Worker(latch, "张三");
Worker w2 = orderThread.new Worker(latch, "李四");
Worker w3 = orderThread.new Worker(latch, "王二");
Boss boss = orderThread.new Boss(latch);
executor.execute(boss);
executor.execute(w3);
executor.execute(w2);
executor.execute(w1);
executor.shutdown();
}
}
老板正在等所有的工人干完活......
王二正在干活...
李四正在干活...
张三正在干活...
李四活干完了!
王二活干完了!
张三活干完了!
工人活都干完了,老板开始检查了!
调用thread.join() 方法必须等thread 执行完毕,当前线程才能继续往下执行,而CountDownLatch通过计数器提供了更灵活的控制,只要检测到计数器为0当前线程就可以往下执行而不用管相应的thread是否执行完毕。
具体比较见文章:http://blog.csdn.net/nyistzp/article/details/51444487
当线程池的线程全部执行完毕后再执行主线程,示例代码如下。
public class ExecuteOrderPractice {
public void orderPractice(){
ExecutorService executorService = Executors.newFixedThreadPool(3);
for(int i = 0; i < 5; i++){
executorService.execute(new Runnable() {
@Override
public void run() {
try{
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " do something");
}catch (InterruptedException e){
e.printStackTrace();
}
}
});
}
executorService.shutdown();
while(true){
if(executorService.isTerminated()){
System.out.println("Finally do something ");
break;
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
new ExecuteOrderPractice().orderPractice();
}
}
pool-1-thread-1 do something
pool-1-thread-3 do something
pool-1-thread-2 do something
pool-1-thread-3 do something
pool-1-thread-1 do something
Finally do something
java 多线程 CountDownLatch与join()方法区别
CountDownLatch使用实例
Java如何判断线程池所有任务是否执行完毕