Java并发编程之CountDownLatch、CyclicBarrier和Semaphore

java的concurrent包为我们提供了一些非常有用的辅助类来帮助我们进行并发编程,比如CountDownLatch,CyclicBarrier和Semaphore,今天就来介绍下这三个辅助类并进行对比:

 

CountDownLatch的介绍和使用可见我之前的博客:

http://wosyingjun.iteye.com/blog/2223933

 

CyclicBarrier的介绍和使用可见我之前的博客

http://wosyingjun.iteye.com/blog/2299857

 

 Semaphore的介绍和使用可见我之前的博客

http://wosyingjun.iteye.com/blog/2299860

 

 

下面对上面说的三个辅助类进行一个总结:

CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同。

CountDownLatch一般用于某个线程等待若干个其他线程执行完任务之后,它才执行。

CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;

CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。

Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。



CountDownLatch,一个同步辅助类,以计数的方式在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

 

CountDownLatch类只提供了一个构造器:

Java代码   收藏代码
  1. public CountDownLatch(int count) {  };  //参数count为计数值  

 


下面这3个方法是CountDownLatch类中最重要的方法:

Java代码   收藏代码
  1. public void await() throws InterruptedException { };//调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行  
  2. public boolean await(long timeout, TimeUnit unit) throws InterruptedException { };//和await()类似,只不过等待一定的时间后count值还没变为0的话就会继续执行  
  3. public void countDown() { };//将count值减1  

 


下面这个例子大家就清楚CountDownLatch的用法了:

Java代码   收藏代码
  1. public class Test {  
  2.      public static void main(String[] args) {      
  3.          final CountDownLatch latch = new CountDownLatch(2);  
  4.          new Thread(){  
  5.              public void run() {  
  6.                  try {  
  7.                      System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");  
  8.                     Thread.sleep(3000);  
  9.                     System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");  
  10.                     latch.countDown();  
  11.                 } catch (InterruptedException e) {  
  12.                     e.printStackTrace();  
  13.                 }  
  14.              };  
  15.          }.start();  
  16.             
  17.          new Thread(){  
  18.              public void run() {  
  19.                  try {  
  20.                      System.out.println("子线程"+Thread.currentThread().getName()+"正在执行");  
  21.                      Thread.sleep(3000);  
  22.                      System.out.println("子线程"+Thread.currentThread().getName()+"执行完毕");  
  23.                      latch.countDown();  
  24.                 } catch (InterruptedException e) {  
  25.                     e.printStackTrace();  
  26.                 }  
  27.              };  
  28.          }.start();  
  29.   
  30.          try {  
  31.             System.out.println("等待2个子线程执行完毕...");  
  32.             latch.await();  
  33.             System.out.println("2个子线程已经执行完毕");  
  34.             System.out.println("继续执行主线程");  
  35.         } catch (InterruptedException e) {  
  36.             e.printStackTrace();  
  37.         }  
  38.      }   
  39. }  

 

执行结果:

Java代码   收藏代码
  1. 线程Thread-0正在执行  
  2. 线程Thread-1正在执行  
  3. 等待2个子线程执行完毕...  
  4. 线程Thread-0执行完毕  
  5. 线程Thread-1执行完毕  
  6. 2个子线程已经执行完毕  
  7. 继续执行主线程  

CyclicBarrier介绍和使用


CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

 

CyclicBarrier类位于java.util.concurrent包下,CyclicBarrier提供2个构造器,参数parties指让多少个线程或者任务等待至barrier状态;参数barrierAction为当这些线程都达到barrier状态时会执行的内容。

Java代码   收藏代码
  1. public CyclicBarrier(int parties, Runnable barrierAction) {}  
  2. public CyclicBarrier(int parties) {}  

 

 

CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:

第一个版本比较常用,用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;

第二个版本是让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。

Java代码   收藏代码
  1. public int await() throws InterruptedException, BrokenBarrierException { };  
  2. public int await(long timeout, TimeUnit unit)throws InterruptedException,BrokenBarrierException,TimeoutException { };  

 

 

假若有若干个线程都要进行写数据操作,并且只有所有线程都完成写数据操作之后,这些线程才能继续做后面的事情,此时就可以利用CyclicBarrier了:

Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         int N = 4;  
  5.         CyclicBarrier barrier  = new CyclicBarrier(N);  
  6.         for(int i=0;i<N;i++)  
  7.             new Writer(barrier).start();  
  8.     }   
  9.   
  10.     static class Writer extends Thread{  
  11.         private CyclicBarrier cyclicBarrier;  
  12.         public Writer(CyclicBarrier cyclicBarrier) {  
  13.             this.cyclicBarrier = cyclicBarrier;  
  14.         }  
  15.   
  16.         @Override  
  17.         public void run() {  
  18.             System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");  
  19.             try {  
  20.                 Thread.sleep(5000);//以睡眠来模拟写入数据操作  
  21.                 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");  
  22.                 cyclicBarrier.await();  
  23.             } catch (InterruptedException e) {  
  24.                 e.printStackTrace();  
  25.             }catch(BrokenBarrierException e){  
  26.                 e.printStackTrace();  
  27.             }  
  28.             System.out.println("所有线程写入完毕,继续处理其他任务...");  
  29.         }  
  30.     }  
  31. }  

 

从下面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。当所有线程线程写入操作完毕之后,才会进行后续的操作了。

Java代码   收藏代码
  1. 线程Thread-0正在写入数据...  
  2. 线程Thread-3正在写入数据...  
  3. 线程Thread-2正在写入数据...  
  4. 线程Thread-1正在写入数据...  
  5. 线程Thread-2写入数据完毕,等待其他线程写入完毕  
  6. 线程Thread-0写入数据完毕,等待其他线程写入完毕  
  7. 线程Thread-3写入数据完毕,等待其他线程写入完毕  
  8. 线程Thread-1写入数据完毕,等待其他线程写入完毕  
  9. 所有线程写入完毕,继续处理其他任务...  
  10. 所有线程写入完毕,继续处理其他任务...  
  11. 所有线程写入完毕,继续处理其他任务...  
  12. 所有线程写入完毕,继续处理其他任务...  

 

 

如果说想在所有线程写入操作完之后,进行额外的其他操作可以为CyclicBarrier提供Runnable参数

Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         int N = 4;  
  5.         CyclicBarrier barrier  = new CyclicBarrier(N,new Runnable() {  
  6.             @Override  
  7.             public void run() {  
  8.                 System.out.println("当前线程"+Thread.currentThread().getName());      
  9.             }  
  10.         });  
  11.         for(int i=0;i<N;i++)  
  12.             new Writer(barrier).start();  
  13.     }   
  14.   
  15.     static class Writer extends Thread{  
  16.         private CyclicBarrier cyclicBarrier;  
  17.         public Writer(CyclicBarrier cyclicBarrier) {  
  18.             this.cyclicBarrier = cyclicBarrier;  
  19.         }  
  20.    
  21.         @Override  
  22.         public void run() {  
  23.             System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");  
  24.             try {  
  25.                 Thread.sleep(5000);      //以睡眠来模拟写入数据操作  
  26.                 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");  
  27.                 cyclicBarrier.await();  
  28.             } catch (InterruptedException e) {  
  29.                 e.printStackTrace();  
  30.             }catch(BrokenBarrierException e){  
  31.                 e.printStackTrace();  
  32.             }  
  33.             System.out.println("所有线程写入完毕,继续处理其他任务...");  
  34.         }  
  35.     }  
  36. }  

 

从结果可以看出,当四个线程都到达barrier状态后,会从四个线程中选择一个线程去执行Runnable。

Java代码   收藏代码
  1. 线程Thread-0正在写入数据...  
  2. 线程Thread-1正在写入数据...  
  3. 线程Thread-2正在写入数据...  
  4. 线程Thread-3正在写入数据...  
  5. 线程Thread-0写入数据完毕,等待其他线程写入完毕  
  6. 线程Thread-1写入数据完毕,等待其他线程写入完毕  
  7. 线程Thread-2写入数据完毕,等待其他线程写入完毕  
  8. 线程Thread-3写入数据完毕,等待其他线程写入完毕  
  9. 当前线程Thread-3  
  10. 所有线程写入完毕,继续处理其他任务...  
  11. 所有线程写入完毕,继续处理其他任务...  
  12. 所有线程写入完毕,继续处理其他任务...  
  13. 所有线程写入完毕,继续处理其他任务...  

 

 

下面举个例子看一下为await指定时间的效果:

Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         int N = 4;  
  5.         CyclicBarrier barrier  = new CyclicBarrier(N);  
  6.         for(int i=0;i<N;i++) {  
  7.             if(i<N-1)  
  8.                 new Writer(barrier).start();  
  9.             else {  
  10.                 try {  
  11.                     Thread.sleep(5000);  
  12.                 } catch (InterruptedException e) {  
  13.                     e.printStackTrace();  
  14.                 }  
  15.                 new Writer(barrier).start();  
  16.             }  
  17.         }  
  18.     }   
  19.   
  20.     static class Writer extends Thread{  
  21.         private CyclicBarrier cyclicBarrier;  
  22.         public Writer(CyclicBarrier cyclicBarrier) {  
  23.             this.cyclicBarrier = cyclicBarrier;  
  24.         }  
  25.         @Override  
  26.         public void run() {  
  27.             System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");  
  28.             try {  
  29.                 Thread.sleep(5000);      //以睡眠来模拟写入数据操作  
  30.                 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");  
  31.                 try {  
  32.                     cyclicBarrier.await(2000, TimeUnit.MILLISECONDS);  
  33.                 } catch (TimeoutException e) {                
  34.                     e.printStackTrace();  
  35.                 }  
  36.             } catch (InterruptedException e) {  
  37.                 e.printStackTrace();  
  38.             }catch(BrokenBarrierException e){  
  39.                 e.printStackTrace();  
  40.             }  
  41.             System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");  
  42.         }  
  43.     }  
  44. }  

 

上面的代码在main方法的for循环中,故意让最后一个线程启动延迟,因为在前面三个线程都达到barrier之后,等待了指定的时间发现第四个线程还没有达到barrier,就抛出异常并继续执行后面的任务。

Java代码   收藏代码
  1. 线程Thread-0正在写入数据...  
  2. 线程Thread-2正在写入数据...  
  3. 线程Thread-1正在写入数据...  
  4. 线程Thread-2写入数据完毕,等待其他线程写入完毕  
  5. 线程Thread-0写入数据完毕,等待其他线程写入完毕  
  6. 线程Thread-1写入数据完毕,等待其他线程写入完毕  
  7. 线程Thread-3正在写入数据...  
  8. java.util.concurrent.TimeoutException  
  9. Thread-1所有线程写入完毕,继续处理其他任务...  
  10. Thread-0所有线程写入完毕,继续处理其他任务...  
  11.     at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)  
  12.     at java.util.concurrent.CyclicBarrier.await(Unknown Source)  
  13.     at com.cxh.test1.Test$Writer.run(Test.java:58)  
  14. java.util.concurrent.BrokenBarrierException  
  15.     at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)  
  16.     at java.util.concurrent.CyclicBarrier.await(Unknown Source)  
  17.     at com.cxh.test1.Test$Writer.run(Test.java:58)  
  18. java.util.concurrent.BrokenBarrierException  
  19.     at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)  
  20.     at java.util.concurrent.CyclicBarrier.await(Unknown Source)  
  21.     at com.cxh.test1.Test$Writer.run(Test.java:58)  
  22. Thread-2所有线程写入完毕,继续处理其他任务...  
  23. java.util.concurrent.BrokenBarrierException  
  24. 线程Thread-3写入数据完毕,等待其他线程写入完毕  
  25.     at java.util.concurrent.CyclicBarrier.dowait(Unknown Source)  
  26.     at java.util.concurrent.CyclicBarrier.await(Unknown Source)  
  27.     at com.cxh.test1.Test$Writer.run(Test.java:58)  
  28. Thread-3所有线程写入完毕,继续处理其他任务...  

 

 

另外CyclicBarrier是可以重用的,看下面这个例子:

Java代码   收藏代码
  1. public class Test {  
  2.   
  3.     public static void main(String[] args) {  
  4.         int N = 4;  
  5.         CyclicBarrier barrier  = new CyclicBarrier(N);  
  6.         for(int i=0;i<N;i++) {  
  7.             new Writer(barrier).start();  
  8.         }  
  9.         try {  
  10.             Thread.sleep(25000);  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         System.out.println("CyclicBarrier重用");  
  15.         for(int i=0;i<N;i++) {  
  16.             new Writer(barrier).start();  
  17.         }  
  18.     }   
  19.   
  20.     static class Writer extends Thread{  
  21.         private CyclicBarrier cyclicBarrier;  
  22.         public Writer(CyclicBarrier cyclicBarrier) {  
  23.             this.cyclicBarrier = cyclicBarrier;  
  24.         }  
  25.         @Override  
  26.         public void run() {  
  27.             System.out.println("线程"+Thread.currentThread().getName()+"正在写入数据...");  
  28.             try {  
  29.                 Thread.sleep(5000); //以睡眠来模拟写入数据操作  
  30.                 System.out.println("线程"+Thread.currentThread().getName()+"写入数据完毕,等待其他线程写入完毕");  
  31.                 cyclicBarrier.await();  
  32.             } catch (InterruptedException e) {  
  33.                 e.printStackTrace();  
  34.             }catch(BrokenBarrierException e){  
  35.                 e.printStackTrace();  
  36.             }  
  37.             System.out.println(Thread.currentThread().getName()+"所有线程写入完毕,继续处理其他任务...");  
  38.         }  
  39.     }  
  40. }  

 

 从执行结果可以看出,在初次的4个线程越过barrier状态后,又可以用来进行新一轮的使用。而CountDownLatch无法进行重复使用。

Java代码   收藏代码
  1. 线程Thread-0正在写入数据...  
  2. 线程Thread-1正在写入数据...  
  3. 线程Thread-3正在写入数据...  
  4. 线程Thread-2正在写入数据...  
  5. 线程Thread-1写入数据完毕,等待其他线程写入完毕  
  6. 线程Thread-3写入数据完毕,等待其他线程写入完毕  
  7. 线程Thread-2写入数据完毕,等待其他线程写入完毕  
  8. 线程Thread-0写入数据完毕,等待其他线程写入完毕  
  9. Thread-0所有线程写入完毕,继续处理其他任务...  
  10. Thread-3所有线程写入完毕,继续处理其他任务...  
  11. Thread-1所有线程写入完毕,继续处理其他任务...  
  12. Thread-2所有线程写入完毕,继续处理其他任务...  
  13. CyclicBarrier重用  
  14. 线程Thread-4正在写入数据...  
  15. 线程Thread-5正在写入数据...  
  16. 线程Thread-6正在写入数据...  
  17. 线程Thread-7正在写入数据...  
  18. 线程Thread-7写入数据完毕,等待其他线程写入完毕  
  19. 线程Thread-5写入数据完毕,等待其他线程写入完毕  
  20. 线程Thread-6写入数据完毕,等待其他线程写入完毕  
  21. 线程Thread-4写入数据完毕,等待其他线程写入完毕  
  22. Thread-4所有线程写入完毕,继续处理其他任务...  
  23. Thread-5所有线程写入完毕,继续处理其他任务...  
  24. Thread-6所有线程写入完毕,继续处理其他任务...  
  25. Thread-7所有线程写入完毕,继续处理其他任务...  

 

 

一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。

 

Semaphore类位于java.util.concurrent包下,它提供了2个构造器:

Java代码   收藏代码
  1. //参数permits表示许可数目,即同时可以允许多少线程进行访问  
  2. public Semaphore(int permits) {  
  3.     sync = new NonfairSync(permits);  
  4. }  
  5. //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可  
  6. public Semaphore(int permits, boolean fair) {  
  7.     sync = (fair)? new FairSync(permits) : new NonfairSync(permits);  
  8. }  

 

 

Semaphore类中比较重要的几个方法,首先是acquire()、release()方法:

acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。

release()用来释放许可。注意,在释放许可之前,必须先获获得许可。

Java代码   收藏代码
  1. public void acquire() throws InterruptedException {  }     //获取一个许可  
  2. public void acquire(int permits) throws InterruptedException { }    //获取permits个许可  
  3. public void release() { }          //释放一个许可  
  4. public void release(int permits) { }    //释放permits个许可  

 

 

这4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法:

Java代码   收藏代码
  1. //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false  
  2. public boolean tryAcquire() { };  
  3. //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false  
  4. public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { };   
  5. //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false  
  6. public boolean tryAcquire(int permits) { };   
  7. //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true  
  8. public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { };  
  9. //得到当前可用的许可数目  
  10. public int availablePermits();  

 

 

假若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:

Java代码   收藏代码
  1. public class Test {  
  2.     public static void main(String[] args) {  
  3.         int N = 8//工人数  
  4.         Semaphore semaphore = new Semaphore(5); //机器数目  
  5.         for(int i=0;i<N;i++)  
  6.             new Worker(i,semaphore).start();  
  7.     }      
  8.     static class Worker extends Thread{  
  9.         private int num;  
  10.         private Semaphore semaphore;  
  11.         public Worker(int num,Semaphore semaphore){  
  12.             this.num = num;  
  13.             this.semaphore = semaphore;  
  14.         }          
  15.         @Override  
  16.         public void run() {  
  17.             try {  
  18.                 semaphore.acquire();  
  19.                 System.out.println("工人"+this.num+"占用一个机器在生产...");  
  20.                 Thread.sleep(2000);  
  21.                 System.out.println("工人"+this.num+"释放出机器");  
  22.                 semaphore.release();              
  23.             } catch (InterruptedException e) {  
  24.                 e.printStackTrace();  
  25.             }  
  26.         }  
  27.     }  
  28. }  

 

运行结果:

Java代码   收藏代码
  1. 工人0占用一个机器在生产...  
  2. 工人1占用一个机器在生产...  
  3. 工人2占用一个机器在生产...  
  4. 工人4占用一个机器在生产...  
  5. 工人5占用一个机器在生产...  
  6. 工人0释放出机器  
  7. 工人2释放出机器  
  8. 工人3占用一个机器在生产...  
  9. 工人7占用一个机器在生产...  
  10. 工人4释放出机器  
  11. 工人5释放出机器  
  12. 工人1释放出机器  
  13. 工人6占用一个机器在生产...  
  14. 工人3释放出机器  
  15. 工人7释放出机器  
  16. 工人6释放出机器  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值