学习Java5.0 并发编程包--线程工具类

并发包Java.util.concurrent不仅提供了线程池相关的接口和类,同时也提供了几个很有用的工具类!这里打算介绍4个,包括Semaphore,CyclicBarrier,CountDownLatch,Exchanger。在实际编码中,进行多线程的并发互斥和同步控制,使用这些工具类可以节省不少代码!

【Semaphore】

信号灯工具类。我们想想以前控制线程互斥的方式:synchronized & Lock,都达到了什么效果? synchronized关键字,保证了一段多线程敏感代码一次只会有一个线程运行,Lock中“写锁”也会是这个效果,多个持有“读锁”的线程就可以多线程并发访问资源。但我们现在有这个需求,控制某个资源最多有几个线程访问。如果自己实现这个需求,还是需要一定的代码量的,利用Semaphore就可以轻松实现这个需求,我们看这个例子:

[java] view plain copy
 
  1. package cn.test;  
  2.   
  3. import java.util.concurrent.Semaphore;  
  4.   
  5. public class SemaphoreTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.           
  9.         // 创建一个资源,最大允许2个线程同时并发  
  10.         final Resources resource = new Resources(2);  
  11.         // 起5个线程,开始处理资源!  
  12.         for(int i=0; i<5; i++){  
  13.               
  14.             new Thread(new Runnable(){  
  15.   
  16.                 @Override  
  17.                 public void run() {  
  18.                       
  19.                     resource.processResource();  
  20.                       
  21.                 }}, "Thread " + (i+1)).start();  
  22.         }  
  23.     }  
  24.       
  25.     /** 
  26.      * 资源类,该资源允许多线程并发访问!但为了保证可用性,外界可以设定最多几个线程进行并发! 
  27.      */  
  28.     static class Resources {  
  29.           
  30.         // 信号灯,用于控制该资源的并发访问数量  
  31.         Semaphore semaphore = null;  
  32.         public Resources(int concurrentNum){  
  33.               
  34.             if(concurrentNum < 1){  
  35.                   
  36.                 throw new IllegalArgumentException("该资源的并发数量应该>0!");  
  37.             }  
  38.             semaphore = new Semaphore(concurrentNum);  
  39.         }  
  40.   
  41.         public void processResource() {  
  42.               
  43.             System.out.println(Thread.currentThread().getName() + " 正试图获取准许进入资源.....");  
  44.             try {  
  45.                 // 获取一个信号灯,如果没有了,则线程阻塞在这里  
  46.                 semaphore.acquire();  
  47.                 System.out.println(Thread.currentThread().getName() + " 已获取操作资源许可,准备操作!");  
  48.                 // 进行资源操作,这里让线程sleep 代表耗时任务  
  49.                 Thread.sleep(2000);  
  50.                 System.out.println(Thread.currentThread().getName() + " 执行完毕,准备释放信号灯!");  
  51.                 semaphore.release();  
  52.                   
  53.             } catch (InterruptedException e) {  
  54.                 e.printStackTrace();  
  55.             }  
  56.         }  
  57.     }  
  58. }  


输出为:

[java] view plain copy
 
  1. Thread 1 正试图获取准许进入资源.....  
  2. Thread 3 正试图获取准许进入资源.....  
  3. Thread 4 正试图获取准许进入资源.....  
  4. Thread 2 正试图获取准许进入资源.....  
  5. Thread 3 已获取操作资源许可,准备操作!  
  6. Thread 1 已获取操作资源许可,准备操作!  
  7. Thread 5 正试图获取准许进入资源.....  
  8. Thread 3 执行完毕,准备释放信号灯!  
  9. Thread 1 执行完毕,准备释放信号灯!  
  10. Thread 2 已获取操作资源许可,准备操作!  
  11. Thread 4 已获取操作资源许可,准备操作!  
  12. Thread 4 执行完毕,准备释放信号灯!  
  13. Thread 2 执行完毕,准备释放信号灯!  
  14. Thread 5 已获取操作资源许可,准备操作!  
  15. Thread 5 执行完毕,准备释放信号灯!  

我们这个资源最多允许两个线程进入,我们看,开始是4个线程同时申请进入,最后只有两个线程申请成功!只有执行完毕,并且释放信号灯,其他线程才可以成功申请进入!创建Semaphore时,需要设定信号灯的数量,这个数量最终用于控制线程的数量!调用Semaphore对象的acquire()方法来申请信号灯,如果还有,则申请成功,否则线程就会阻塞在这里!线程执行完毕后,要调用Semaphore对象的release()方法来释放信号灯!通过Semaphore我们可以很轻松地对某个资源的操作线程数进行控制!

Semaphore semaphore = new Semaphore(1)。这个Semaphore对象的作用和synchronized的功效一样!但他有个优点:即某个线程可以调用semaphore的release方法来释放其他线程把持的信号灯!对于synchronized修饰的代码段,如果一个线程进入后,因某种情况如死锁导致无法正常从方法块中出来时,其他线程只能阻塞等待,必要时可能需要人工重启解决。如果使用Semaphore,可以设定一个线程进行监控,如果某个其他线程长时间把持信号灯不放,就可以认为其出现异常情况,可以主动干预释放其把持的信号灯!这种特性的使用,需要根据具体情况仔细才可使用!

【CyclicBarrier】

“可循环的拦阻器”。我们直接用一个例子来解释其用法。假设,一个任务由两个子任务组成,每个子任务包含两部分,子任务2必须要等子任务1完成后才能开始!我们有两个线程,安排线程1执行子任务1第1部分,子任务2第1部分,线程2执行子任务1第2部分,子任务2第2部分,两个线程共同将子任务1完成后才能开始执行子任务2,我们可以通过CyclicBarrier来实现:

[java] view plain copy
 
  1. package cn.test;  
  2.   
  3. import java.util.concurrent.BrokenBarrierException;  
  4. import java.util.concurrent.CyclicBarrier;  
  5.   
  6. public class CyclicBarrierTest {  
  7.   
  8.     public static void main(String[] args) {  
  9.           
  10.         /** 
  11.          * 创建任务,该任务由两个子任务组成,任务1和任务2.每个子任务包含两部分,任务2要依赖于任务1。 
  12.          * 即任务2开始执行时,任务1必须完成。我们创建两个线程来做这个。任务内部通过CyclicBarrier来 
  13.          * 控制子任务的关系! 
  14.          */  
  15.         final MyTask task = new MyTask();  
  16.         new Thread(new Runnable(){  
  17.   
  18.             @Override  
  19.             public void run() {  
  20.                   
  21.                 task.task1Part1();  
  22.                 task.task2Part1();  
  23.                   
  24.             }}, "线程一").start();  
  25.         new Thread(new Runnable(){  
  26.   
  27.             @Override  
  28.             public void run() {  
  29.                   
  30.                 task.task1Part2();  
  31.                 task.task2Part2();  
  32.                   
  33.             }}, "线程二").start();  
  34.           
  35.     }  
  36.       
  37.     static class MyTask {  
  38.           
  39.         CyclicBarrier barrier = new CyclicBarrier(2);  
  40.           
  41.         public void task1Part1() {  
  42.               
  43.             System.out.println(Thread.currentThread().getName() + " 开始执行任务1的部分1!");  
  44.             try {  
  45.                 Thread.sleep(1000);  
  46.             } catch (InterruptedException e) {  
  47.                 e.printStackTrace();  
  48.             }  
  49.             System.out.println(Thread.currentThread().getName() + " 将任务1的第1部分执行完毕!!");  
  50.         }  
  51.           
  52.         public void task1Part2() {  
  53.               
  54.             System.out.println(Thread.currentThread().getName() + " 开始执行任务1的部分2!");  
  55.             try {  
  56.                 Thread.sleep(3000);  
  57.             } catch (InterruptedException e) {  
  58.                 e.printStackTrace();  
  59.             }  
  60.             System.out.println(Thread.currentThread().getName() + " 将任务1的第2部分执行完毕!!");  
  61.         }  
  62.           
  63.         public void task2Part1() {  
  64.               
  65.             try {  
  66.                 barrier.await();  
  67.                 System.out.println(Thread.currentThread().getName() + " 开始执行任务2的部分1!");  
  68.                 Thread.sleep(1000);  
  69.                 System.out.println(Thread.currentThread().getName() + " 将任务2的第1部分执行完毕!!");  
  70.             } catch (InterruptedException e) {  
  71.                 e.printStackTrace();  
  72.             } catch (BrokenBarrierException e) {  
  73.                 e.printStackTrace();  
  74.             }  
  75.         }  
  76.           
  77.         public void task2Part2() {  
  78.               
  79.             try {  
  80.                 barrier.await();  
  81.                 System.out.println(Thread.currentThread().getName() + " 开始执行任务2的部分2!");  
  82.                 Thread.sleep(2000);  
  83.                 System.out.println(Thread.currentThread().getName() + " 将任务2的第2部分执行完毕!!");  
  84.             } catch (InterruptedException e) {  
  85.                 e.printStackTrace();  
  86.             } catch (BrokenBarrierException e) {  
  87.                 e.printStackTrace();  
  88.             }  
  89.         }  
  90.           
  91.           
  92.     }  
  93.   
  94. }  


输出结果为:

[java] view plain copy
 
  1. 线程一 开始执行任务1的部分1!  
  2. 线程二 开始执行任务1的部分2!  
  3. 线程一 将任务1的第1部分执行完毕!!  
  4. 线程二 将任务1的第2部分执行完毕!!  
  5. 线程二 开始执行任务2的部分2!  
  6. 线程一 开始执行任务2的部分1!  
  7. 线程一 将任务2的第1部分执行完毕!!  
  8. 线程二 将任务2的第2部分执行完毕!!  

创建CyclicBarrier对象,需要一个整形参数,表示这个对象一次阻拦多少个线程。当阻拦的线程数一到,才准许放行!调用CyclicBarrier对象的await()方法,如果此时线程数没到,则线程阻塞在这里,否则,所有阻塞的线程均放行!CyclicBarrier可以反复使用,放行一批后,另一批过来,又是重复这个过程!!

CountDownLatch 和 Exchanger,我们下篇再讲!

 

posted @ 2016-12-02 15:29  天涯海角路  阅读(132)  评论(0)    收藏  举报