线程执行顺序——CountDownLatch、CyclicBarrier 、join()、线程池

文章目录

  • 问题
  • 方法一——CyclicBarrier
    • 实现原理
    • CyclicBarrier 和 CountDownLatch 比较
    • 示例
    • 运行结果
  • 方法二——join
    • join 实现
    • 示例
    • 运行结果
  • 方法三——CountDownLatch
    • 使用场景
    • 示例
    • 运行结果
    • CountDownLatch 与 join 比较
  • 方法四——线程池
    • 运行结果
  • 参考资料

问题

本文主要围绕一个问题展开:线程执行顺序,比如某个线程在其他线程并发执行完毕后最后执行,这里分别用 CountDownLatch、CyclicBarrier 、join()、线程池来实现。

方法一——CyclicBarrier

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 方法中返回。

CyclicBarrier 和 CountDownLatch 比较

  • 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

join() 是 Thread 类的一个方法,join() 方法的作用是等待当前线程结束,也即让“主线程”等待“子线程”结束之后才能继续运行。t.join() 方法阻塞调用此方法的线程 (calling thread),直到线程 t完成,此线程再继续(看起来和同步调用类似);通常用于在 main 主线程内,等待其它线程完成后再继续执行 main 主线程。

join 实现

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

方法三——CountDownLatch

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();

    }

}

运行结果

老板正在等所有的工人干完活......
王二正在干活...
李四正在干活...
张三正在干活...
李四活干完了!
王二活干完了!
张三活干完了!
工人活都干完了,老板开始检查了!

CountDownLatch 与 join 比较

调用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如何判断线程池所有任务是否执行完毕

你可能感兴趣的:(Java,Java并发编程)