Java并发编程之CountDownLatch,CyclicBarrier实现一组线程相互等待、唤醒

时间:2021-07-03 20:52:31

java多线程应用场景不少,有时自己编写代码又不太容易实现,好在concurrent包提供了不少实现类,还有google的guava包更是提供了一些最佳实践,这让我们在面对一些多线程的场景时,有了不少的选择。

这里主要是看几个涉及到多线程等待的工具类。

一 CountDownLatch 一个或多个线程等待其他线程达到某一个目标后,再进行自己的下一步工作。而被等待的“其他线程”达到这个目标后,也继续自己下面的任务

看起来虽然比较绕,但是还算能理解。看几个场景: 跑步比赛,裁判需要等到所有的运动员(“其他线程”)都跑到终点(达到目标),才能去算排名和颁奖。 模拟并发,我需要启动100个线程去同时访问某一个地址,我希望它们能同时并发,而不是一个一个的去执行。
用法很简单,只有一个构造方法用于指明计数数量,然后就是await用于线程等待,countDown用于将计数器减1.
public CountDownLatch(int count) {  };  //参数count为计数值
public void await() throws InterruptedException { };   //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };  //和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行public void countDown() { };  //将count值减1
工作原理就是让“其他线程”在合适的地方进行await等待,直到所有线程达到了某一个目标,然后再一起释放。
看代码: 赛跑的代码
import java.util.Random;import java.util.concurrent.CountDownLatch;/** * Created by wuwf on 17/7/17. * 一个线程或多个线程等待其他线程运行达到某一目标后进行自己的下一步工作,而被等待的“其他线程”达到这个目标后继续自己下面的任务。 * <p/> * */public class TestCountDownLatch {    private CountDownLatch countDownLatch = new CountDownLatch(4);    public static void main(String[] args) {        TestCountDownLatch testCountDownLatch = new TestCountDownLatch();        testCountDownLatch.begin();    }    /**     * 运动员     */    private class Runner implements Runnable {        private int result;        public Runner(int result) {            this.result = result;        }        @Override        public void run() {            try {                //模拟跑了多少秒,1-3之间随机一个数                Thread.sleep(result * 1000);                System.out.println("运动员" + Thread.currentThread().getId() + "跑了" + result + "秒");                //跑完了就计数器减1                countDownLatch.countDown();            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }    private void begin() {        System.out.println("赛跑开始");        Random random = new Random(System.currentTimeMillis());        for (int i = 0; i < 4; i++) {            //随机设置每个运动员跑多少秒结束            int result = random.nextInt(3) + 1;            new Thread(new Runner(result)).start();        }        try {            countDownLatch.await();        } catch (InterruptedException e) {            e.printStackTrace();        }        System.out.println("所有人都跑完了,裁判开始算成绩");    }}
这段代码就是一个主线程,等待其他多个子线程完成某个任务后,再去执行下面的逻辑。

模拟并发的代码
import java.util.concurrent.CountDownLatch;/** * Created by wuwf on 17/7/18. * 模拟N个线程同时启动 */public class TestManyThread {    private CountDownLatch countDownLatch = new CountDownLatch(200);    public static void main(String[] args) {        new TestManyThread().begin();    }        public void begin() {        for (int i = 0; i < 200; i++) {            new Thread(new UserThread()).start();            countDownLatch.countDown();        }        try {            Thread.sleep(2000);            System.out.println("线程并发");        } catch (InterruptedException e) {            e.printStackTrace();        }    }    private class UserThread implements Runnable {        @Override        public void run() {            try {                //等待所有线程                countDownLatch.await();                //TODO 在这里做客户端请求,譬如访问数据库之类的操作                            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

需要注意,上面两个例子是不同的方式,一个是主线程等待其他线程达到某个目标后,自己再去完成一件事;第二个例子是多个线程达到某个目标后,继续完成各自的后续任务。
其中由第二个例子引申出下一个并发工具类。

二 CyclicBarrier 实现让一组线程等待至某个状态之后再全部同时执行,而且当所有等待线程被释放后,CyclicBarrier可以被重复使用。

这个也很好理解,大家约定好一起到XX地址汇合,所有人都到了以后再一起去吃饭。 它有两个构造方法
public CyclicBarrier(int parties, Runnable barrierAction) {} public CyclicBarrier(int parties) {}
parties代表让多少个线程等待,Runnable属性是一个新线程,代表所有线程达到状态、等待完毕后,会执行的任务。
同样的也有两个await方法
public int await() throws InterruptedException, BrokenBarrierException { };public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };
第一个代表线程挂起开始等待,一直等到达到目标状态,第二个代表等待一段指定的时间后,如果还没释放,就直接继续执行,不await了。
看代码
import java.util.concurrent.CyclicBarrier;/** * Created by wuwf on 17/7/18. */public class TestCyclicBarrier {    private CyclicBarrier cyclicBarrier = new CyclicBarrier(5);    public static void main(String[] args) {        new TestCyclicBarrier().begin();    }    public void begin() {        for (int i = 0; i < 5; i++) {            new Thread(new Student()).start();        }    }    private class Student implements Runnable {        @Override        public void run() {            try {                System.out.println("学生" + Thread.currentThread().getId() + "正在赶往XX饭店的路上");                Thread.sleep(2000);                //到了就等着,等其他人都到了,就进饭店                cyclicBarrier.await();            } catch (Exception e) {                e.printStackTrace();            }            System.out.println("大家都到了,进去吃饭吧!");        }    }}

Java并发编程之CountDownLatch,CyclicBarrier实现一组线程相互等待、唤醒
可以看到这个工具类的目的,主要是为了限制一组线程在达到某个条件后,强制进行等待,直到最后一个线程也执行完await之前的逻辑后,再所有线程一起走await后面的。 与CountDownLatch不同,它不需要去自己维护那个CountDown每次减1的操作,与之相反,CyclicBarrier是每次都加1,直到加到构造方法里设定的值。 所以使用这个类一样可以完成上面CountDownLatch的第二个例子,就是模拟N个线程并发,而且用法比CountDownLatch更简单一些。
要说起这两个类的区别,我觉得更多的初衷是CountDownLatch目的是让一个线程等待其他N个线程达到某个条件后,自己再去做某个事(通过CyclicBarrier的第二个构造方法,在新线程里做事可以达到同样的效果)。而CyclicBarrier的目的是让N多线程互相等待直到所有的都达到某个状态,然后这N个线程再继续执行各自后续(通过CountDownLatch在某些场合也能完成类似的效果)。 根据场景选择更简单的那个就好。