CyclicBarrier:
api对CyclicBarrier的描述: 一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。 也就是说他可以使一组线程先等待 然后达到某个条件之后再一起执行,有点map/reduce的感觉。
举个例子: 目前有个int, 分配3个任务线程对他加1 , 最后主任务线程汇集计算结果,代码如下:
private static AtomicInteger i = new AtomicInteger(0); public static void main(String[] args){ CyclicBarrier cb = new CyclicBarrier(3,new Runnable() { //主任务汇集计算结果 public void run() { System.out.println("结果为" + i.get()); } }); ExecutorService es = Executors.newFixedThreadPool(5); es.submit(new SubTask(cb, "线程一")); es.submit(new SubTask(cb, "线程二")); es.submit(new SubTask(cb, "线程三")); es.shutdown(); } //子任务计算 private static class SubTask implements Runnable{ private CyclicBarrier cb; private String msg; public SubTask(CyclicBarrier cb, String msg){ this.cb = cb; this.msg = msg; } public void run() { try { System.out.println(msg + " enter"); i.incrementAndGet(); Thread.sleep(1000l); cb.await(); System.out.println(msg + " quit"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (BrokenBarrierException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
结果:
线程一 enter
线程三 enter
线程二 enter
结果为3
线程三 quit
线程二 quit
线程一 quit
如果定义的参与者线程比实际的线程要少会怎么样? 比如上例中es提交4个任务结果会怎样?
api中描述:因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier 所以如果修改代码如下:
CyclicBarrier cb = new CyclicBarrier(3,new Runnable() { //主任务汇集计算结果 public void run() { System.out.println("结果为" + i.get()); } }); ExecutorService es = Executors.newFixedThreadPool(5); es.submit(new SubTask(cb, "线程一")); es.submit(new SubTask(cb, "线程二")); es.submit(new SubTask(cb, "线程三")); es.submit(new SubTask(cb, "线程四")); es.shutdown();
则结果是运行完先进入的三个线程之后 第四个线程一直堵塞。
可能的输出:
线程一 enter
线程三 enter
线程二 enter
线程四 enter
结果4
线程三 quit
线程四 quit
线程一 quit
如上结果所示 有一个线程一直堵塞中
CountDownLatch:
api对CountDownLatch描述:一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch
。由于调用了 countDown()
方法,所以在当前计数到达零之前,await
方法会一直受阻塞。之后,会释放所有等待的线程,await
的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier
。
由此可见: CountDownLatch与CyclicBarrier的区别是: CyclicBarrier可以重复使用 而CountDownLatch不能重复使用
简单例子如下:
public static void main(String[] args) throws InterruptedException, BrokenBarrierException{ //分配3个子任务去完成 CountDownLatch cdl = new CountDownLatch(3); Thread t = new Thread(new SubTask(cdl, "线程1")); Thread t1 = new Thread(new SubTask(cdl, "线程2")); Thread t2 = new Thread(new SubTask(cdl, "线程3")); t.start(); t1.start(); t2.start(); //在3个子任务完成之前一直等待 cdl.await(); //3个子任务完成之后 主线程获取结果 System.out.print(i.get()); } //子任务计算 private static class SubTask implements Runnable{ private CountDownLatch cb; private String msg; public SubTask(CountDownLatch cb, String msg){ this.cb = cb; this.msg = msg; } public void run() { i.incrementAndGet(); System.out.println(msg + "进入"); try { Thread.sleep(1000l); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } cb.countDown(); } }
结果:
线程2进入
线程1进入
线程3进入
3
还有一个与以上两个类使用方式非常相似的类: Semaphore
public int MAX = 10; public Semaphore s = new Semaphore(MAX); public void semaphoreTest() throws InterruptedException{ Thread thread = new Thread(new Runnable() { @Override public void run() { try{ s.acquire(MAX); for(int i = 0; i<MAX; i++){ s.release(); Thread.sleep(1000L); } }catch(Exception e){ e.printStackTrace(); } } }); thread.start(); Thread.sleep(1000L); while(true){ try{ s.acquire(); System.out.print(1); }catch(Exception e){ break; } } }
这个类最大的作用个人感觉就是把许可数设置为1: new Semaphore(1) 然后充当一个互斥锁。这种方式与lock比较的优势在于: lock只能有持有他的线程来释放,而semaphore实现的互斥锁可由任何线程释放,对死锁恢复非常有帮助