本文共 3908 字,大约阅读时间需要 13 分钟。
在并发编程中,总会有各种各样的需求,根据需求去制定解决方案,才能让我们更好的理解,假如我们有以下两个需求:
有十个线程去执行各自的任务,任务可以分为两个部分,前半部分线程开启就可以执行,后半部分需要需要满足某个条件才能继续往下执行,如果条件暂不满足,那就等待,等到条件满足时就可以开始执行,如果条件满足,就不需要等待,直接往下执行,等十个任务全部做完时才可以在当前线程做其他的事情;
同样有十个线程去执行各自的任务,任务也是分为两个部分,现在要求等所有前半部分执行完后才能开始执行后半部分。
就这么两个需求,第一个可以用CountDownLatch来解决,第二个可以用CyclicBarrier来解决,下面看代码:
public static void main(String[] args) { CountDownLatch start = new CountDownLatch(1); CountDownLatch end = new CountDownLatch(5); for (int i = 0; i < 5; i++) { new Thread(new Worker(start, end)).start(); } try { //这里就是你所需要添加条件的地方,条件满足就执行下面的方法,这是阻塞的线程既可以再次开始了 if(true) { start.countDown(); } end.await(); System.out.println("所有任务做完了,接下来你可以干自己想干的了"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } public static class Worker implements Runnable{ CountDownLatch start; CountDownLatch end; public Worker(CountDownLatch cLatch, CountDownLatch cLatch2) { super(); this.start = cLatch; this.end = cLatch2; } @Override public void run() { try { System.out.println("执行你任务的前半部分 = "+Thread.currentThread().getName()); start.await(); System.out.println("执行你任务的后半部分 = "+Thread.currentThread().getName()); end.countDown(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出结果:
执行你任务的前半部分 = Thread-0执行你任务的前半部分 = Thread-2执行你任务的前半部分 = Thread-1执行你任务的前半部分 = Thread-3执行你任务的后半部分 = Thread-1执行你任务的后半部分 = Thread-3执行你任务的前半部分 = Thread-4执行你任务的后半部分 = Thread-4执行你任务的后半部分 = Thread-2执行你任务的后半部分 = Thread-0所有任务做完了,接下来你可以干自己想干的了
CountDownLatch的构造函数需要传一个数,我们假定是count,这个数意味着可以调用countDown()多少次,我们可以这样去理解,没调用一次countDown(),count就减1,只要count不为0,那么调用它的await()方法时,就会阻塞当前线程,直到count减为0时,之前调用到await()方法的地方就不在阻塞了,直白一点就是,只要count为0,不管什么时候调用到的await()方法都不在阻塞线程了,反之则一直阻塞线程。
public static void main(String[] args) { int num = 10; CyclicBarrier barrier = new CyclicBarrier(num, new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("前半部分任务执行完了,这里在执行点自己任务,执行完后就可以执行后半部分任务了!"); } }); Listlist = new ArrayList<>(); for (int i = 1; i <= num; i++) { Thread thread = new Thread(new CyclicBarrierWorker(i, barrier)); list.add(thread); thread.start(); } //这里是为了让子线程的任务先执行完在执行主线程的任务 for (Thread thread : list) { try { thread.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.println("所有的后半部分任务都执行完了,你可以做自己的事了"); } public static class CyclicBarrierWorker implements Runnable { private int id; private CyclicBarrier barrier; public CyclicBarrierWorker(int id, final CyclicBarrier barrier) { this.id = id; this.barrier = barrier; } @Override public void run() { // TODO Auto-generated method stub try { System.out.println("前半部分任务 id = "+id); barrier.await(); // 大家等待最后一个线程到达 System.out.println("后半部分任务 id = "+id); } catch (InterruptedException | BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
输出结果:
前半部分任务 id = 1前半部分任务 id = 3前半部分任务 id = 2前半部分任务 id = 4前半部分任务 id = 9前半部分任务 id = 5前半部分任务 id = 8前半部分任务 id = 7前半部分任务 id = 10前半部分任务 id = 6前半部分任务执行完了,这里在执行点自己任务,执行完后就可以执行后半部分任务了!后半部分任务 id = 6后半部分任务 id = 4后半部分任务 id = 9后半部分任务 id = 3后半部分任务 id = 2后半部分任务 id = 1后半部分任务 id = 10后半部分任务 id = 7后半部分任务 id = 8后半部分任务 id = 5所有的后半部分任务都执行完了,你可以做自己的事了
CyclicBarrier的构造函数也是需要传入一个数,我们取为count,这也就是说总共有count个栅栏锁,每调用一次它的await()方法时,栅栏锁就少一个,但这时是会阻塞线程的,当栅栏锁减为0时,也就说没有锁了,这时线程就不在阻塞了,这样下来,上面的那两个需求是不是就特别的简单了,如果说是用我们自己定义的锁,那实现起来可就没有这么简单了,这两个例子算是比较简单了,如果你还想更加深入的了解,可以结合下源码,源码不多,方法数也不多,结合这两个例子是很容易理解的。
转载地址:http://bkhai.baihongyu.com/