CountDownLatch、CyclicBarrier、Semaphore、Exchanger
CountDownLatch:
允许N个线程等待其他线程完成执行。无法进行重复使用,只能用一次。
比如有2个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
public class Test { public static void main(String[] args) { final CountDownLatch latch = new CountDownLatch(2); new Thread(){ public void run() { try { System.out.println("子线程"+Thread.currentThread().getName()+"正在执行"); Thread.sleep(3000); System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); new Thread(){ public void run() { try { System.out.println("子线程"+Thread.currentThread().getName()+"正在执行"); Thread.sleep(3000); System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕"); latch.countDown(); } catch (InterruptedException e) { e.printStackTrace(); } }; }.start(); try { System.out.println("等待2个子线程执行完毕..."); latch.await(); System.out.println("2个子线程已经执行完毕"); System.out.println("继续执行主线程"); } catch (InterruptedException e) { e.printStackTrace(); } } }
线程Thread-0正在执行
线程Thread-1正在执行
等待2个子线程执行完毕...
线程Thread-0执行完毕
线程Thread-1执行完毕
2个子线程已经执行完毕
继续执行主线程
CyclicBarrier
实现让N个线程等待至某个状态(达到初始化数量值)之后再全部同时执行,并且要全部线程都执行cyclicBarrier.await();后才执行线程后续逻辑。
在初次的4个线程越过barrier状态后,又可以用来进行新一轮的使用,可重复使用。
比如聚餐,一个人先到了就开始等待,又来一个。。直到全部人到期后,这时才能吃饭,全部吃完后由一个人去结账(runable),最后每个人去做自己的事情。
public class Test { public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据..."); try { Thread.sleep(5000); //以睡眠来模拟写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } System.out.println("所有线程写入完毕,继续处理其他任务..."); } } }
线程Thread-0正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。
当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。
public class Test { public static void main(String[] args) { int N = 4; CyclicBarrier barrier = new CyclicBarrier(N,new Runnable() { @Override public void run() { System.out.println("当前线程"+Thread.currentThread().getName()); } }); for(int i=0;i<N;i++) new Writer(barrier).start(); } static class Writer extends Thread{ private CyclicBarrier cyclicBarrier; public Writer(CyclicBarrier cyclicBarrier) { this.cyclicBarrier = cyclicBarrier; } @Override public void run() { System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据..."); try { Thread.sleep(5000); //以睡眠来模拟写入数据操作 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕"); cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); }catch(BrokenBarrierException e){ e.printStackTrace(); } System.out.println("所有线程写入完毕,继续处理其他任务..."); } } }
线程Thread-0正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2正在写入数据...
线程Thread-3正在写入数据...
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
当前线程Thread-3
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
从结果可以看出,当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。
Semaphore
可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:
public class Test { public static void main(String[] args) { int N = 8; //工人数 Semaphore semaphore = new Semaphore(5); //机器数目 for(int i=0;i<N;i++) new Worker(i,semaphore).start(); } static class Worker extends Thread{ private int num; private Semaphore semaphore; public Worker(int num,Semaphore semaphore){ this.num = num; this.semaphore = semaphore; } @Override public void run() { try { semaphore.acquire(); System.out.println("工人"+this.num+"占用一个机器在生产..."); Thread.sleep(2000); System.out.println("工人"+this.num+"释放出机器"); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } } } }
工人0占用一个机器在生产...
工人1占用一个机器在生产...
工人2占用一个机器在生产...
工人4占用一个机器在生产...
工人5占用一个机器在生产...
工人0释放出机器
工人2释放出机器
工人3占用一个机器在生产...
工人7占用一个机器在生产...
工人4释放出机器
工人5释放出机器
工人1释放出机器
工人6占用一个机器在生产...
工人3释放出机器
工人7释放出机器
工人6释放出机器
下面对上面说的三个辅助类进行一个总结:
1)CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同:
CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;
而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;
另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
2)Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
Exchanger
Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。
当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行。
public class ThreadLocalTest { public static void main(String[] args) { Exchanger<List<Integer>> exchanger = new Exchanger<>(); new Consumer(exchanger).start(); new Producer(exchanger).start(); } } class Producer extends Thread { List<Integer> list = new ArrayList<>(); Exchanger<List<Integer>> exchanger = null; public Producer(Exchanger<List<Integer>> exchanger) { super(); this.exchanger = exchanger; } @Override public void run() { Random rand = new Random(); for(int i=0; i<10; i++) { list.clear(); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); list.add(rand.nextInt(10000)); try { list = exchanger.exchange(list); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } class Consumer extends Thread { List<Integer> list = new ArrayList<>(); Exchanger<List<Integer>> exchanger = null; public Consumer(Exchanger<List<Integer>> exchanger) { super(); this.exchanger = exchanger; } @Override public void run() { for(int i=0; i<10; i++) { try { list = exchanger.exchange(list); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.print(list.get(0)+", "); System.out.print(list.get(1)+", "); System.out.print(list.get(2)+", "); System.out.print(list.get(3)+", "); System.out.println(list.get(4)+", "); } } }
8041, 3278, 7654, 2981, 7529,
7091, 4699, 3542, 9739, 3587,
1981, 3427, 7698, 8519, 4319,
2119, 8179, 4338, 7676, 2617,
2772, 6843, 9765, 8948, 418,
7433, 7846, 9442, 6114, 6475,
9142, 4858, 6511, 6401, 9709,
6466, 9250, 3018, 8256, 8771,
80, 8762, 7367, 8664, 9053,
8008, 5456, 2018, 9806, 8194,