多线程并发库高级应用 之 其它同步工具类

来源:互联网 发布:旅游网络销售范围 编辑:程序博客网 时间:2024/06/05 05:37

目录(?)[+]

笔记摘要:

               这里主要介绍了java5中线程锁技术以外的其他同步工具,首先介绍semaphore:一个计数信号量。用于控制同时访问资源的线程个数,

               CyclicBarrier同步辅助类:从字面意思看是路障,这里用于线程之间的相互等待,到达某点后,继续向下执行,CountDownLatch同步辅

              助类:在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。犹如倒计时计数器,然后是Exchangeer:实现两个

             对象之间数据交换,可阻塞队列:ArrayBlockingQueue,通过阻塞队列间的通信来演示其作用,最后介绍了几个同步集合。



一、Semaphore实现信号灯


        1.、Semaphore可以维护当前访问自身的线程个数,并提供了同步机制,使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许

       的并发访问数。Semaphore 只对可用许可的号码进行计数,并采取相应的行动。 

   

        2、Semaphore实现的功能就像:银行办理业务,一共有5个窗口,但一共有10个客户,一次性最多有5个客户可以进行办理,其他的人必须等候,

     当5客户中的任何一个离开后,在等待的客户中有一个人可以进行业务办理。


        3、Semaphore提供了两种规则:

               一种是公平的:获得资源的先后,按照排队的先后。在构造函数中设置true实现

               一种是野蛮的:谁有本事抢到资源,谁就可以获得资源的使用权。


       4、与传统的互斥锁的异同:

             单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁“,再由另外一个线程释放”锁“,

       这可以应用于死锁恢复的一些场合。


      5、应用场景:共享资源的争夺,例如游戏中选手进入房间的情况。


[java] view plaincopy
  1. import java.util.concurrent.ExecutorService;  
  2. import java.util.concurrent.Executors;  
  3. import java.util.concurrent.Semaphore;  
  4.   
  5. public class SemaphoreTest {  
  6.     public static void main(String[] args) {  
  7.           
  8.   //创建一个可根据需要创建新线程的线程池  
  9.   ExecutorService service = Executors.newCachedThreadPool();  
  10.         final  Semaphore sp = new Semaphore(3);  
  11.           
  12.   //创建10个线程  
  13.   for(int i=0;i<10;i++){  
  14.             Runnable runnable = new Runnable(){  
  15.                     public void run(){  
  16.                     try {  
  17.                         sp.acquire();   //获取灯,即许可权  
  18.                     } catch (InterruptedException e1) {  
  19.                         e1.printStackTrace();  
  20.                     }  
  21.                     System.out.println("线程" + Thread.currentThread().getName() +   
  22.                             "进入,当前已有" + (3-sp.availablePermits()) + "个并发");  
  23.                     try {  
  24.                         Thread.sleep((long)(Math.random()*10000));  
  25.                     } catch (InterruptedException e) {  
  26.                         e.printStackTrace();  
  27.                     }  
  28.                     System.out.println("线程" + Thread.currentThread().getName() +   
  29.                             "即将离开");                      
  30.                     sp.release();   // 释放一个许可,将其返回给信号量  
  31.   
  32.                     //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元  
  33.                     System.out.println("线程" + Thread.currentThread().getName() +   
  34.                             "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");                      
  35.                 }  
  36.             };  
  37.             service.execute(runnable);            
  38.         }  
  39.     }  
  40.   
  41. }  


二、其他同步工具类:


CyclicBarrier


          1、一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,

             这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。 


          2、CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障

            点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。 


示例:

       3个线程到达某个集合点后再向下执行,使用await方法实现

[java] view plaincopy
  1. import java.util.concurrent.CyclicBarrier;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4.   
  5. public class CyclicBarrierTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.         ExecutorService service = Executors.newCachedThreadPool();  
  9.         final  CyclicBarrier cb = new CyclicBarrier(3);  
  10.         for(int i=0;i<3;i++){  
  11.             Runnable runnable = new Runnable(){  
  12.                     public void run(){  
  13.                     try {  
  14.                         Thread.sleep((long)(Math.random()*10000));    
  15.                         System.out.println("线程" + Thread.currentThread().getName() +   
  16.                                 "即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                         
  17.                         cb.await();//在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。  
  18.                           
  19.                         Thread.sleep((long)(Math.random()*10000));    
  20.                         System.out.println("线程" + Thread.currentThread().getName() +   
  21.                                 "即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));  
  22.                         cb.await();   
  23.                         Thread.sleep((long)(Math.random()*10000));    
  24.                         System.out.println("线程" + Thread.currentThread().getName() +   
  25.                                 "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                       
  26.                         cb.await();                       
  27.                     } catch (Exception e) {  
  28.                         e.printStackTrace();  
  29.                     }                 
  30.                 }  
  31.             };  
  32.             service.execute(runnable);  
  33.         }  
  34.         service.shutdown();  
  35.     }  
  36. }  

CountDownLatch


        1、一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。犹如倒计时计数器,调用CountDownLatch对象

       的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。


        2、可以实现一个人(也可以是多个人)等待其他所有人都来通知他,也可以实现一个人通知多个人的效果,类似裁判一声口令,运动员开始奔跑

          (一对多),或者所有运送员都跑到终点后裁判才可以公布结果(多对一)。


        3、用指定的计数 初始化 CountDownLatch。在调用 countDown() 方法之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所

          有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 


示例:

       实现运动员比赛的效果

[java] view plaincopy
  1. import java.util.concurrent.CountDownLatch;  
  2. import java.util.concurrent.CyclicBarrier;  
  3. import java.util.concurrent.ExecutorService;  
  4. import java.util.concurrent.Executors;  
  5.   
  6. public class CountdownLatchTest {  
  7.   
  8.     public static void main(String[] args) {  
  9.           
  10.   ExecutorService service = Executors.newCachedThreadPool();  
  11.     
  12.   //构造一个用给定计数初始化的 CountDownLatch,相当于裁判的口哨  
  13.   final CountDownLatch cdOrder = new CountDownLatch(1);  
  14.     
  15.   //相当于定义3个运行员  
  16.         final CountDownLatch cdAnswer = new CountDownLatch(3);  
  17.         for (int i = 0; i < 3; i++) {  
  18.             Runnable runnable = new Runnable() {  
  19.                 public void run() {  
  20.                     try {  
  21.                         System.out.println("线程"  
  22.                                 + Thread.currentThread().getName() + "正准备接受命令");  
  23.   
  24.                         // 等待发令枪  
  25.                         cdOrder.await();//使当前线程在锁存器倒计数至零之前一直等待  
  26.   
  27.                         System.out.println("线程"  
  28.                                 + Thread.currentThread().getName() + "已接受命令");  
  29.                         Thread.sleep((long) (Math.random() * 10000));  
  30.                         System.out  
  31.                                 .println("线程"  
  32.                                         + Thread.currentThread().getName()  
  33.                                         + "回应命令处理结果");  
  34.   
  35.                         // 各个运动员完报告成绩之后,通知裁判  
  36.                         cdAnswer.countDown();//递减锁存器的计数,如果计数到达零,则释放所有等待的线程  
  37.   
  38.                     } catch (Exception e) {  
  39.                         e.printStackTrace();  
  40.                     }  
  41.                 }  
  42.             };  
  43.             service.execute(runnable);  
  44.         }  
  45.         try {  
  46.             Thread.sleep((long) (Math.random() * 10000));  
  47.   
  48.             System.out.println("线程" + Thread.currentThread().getName()  
  49.                     + "即将发布命令");  
  50.             // 发令枪打响,比赛开始  
  51.             cdOrder.countDown();  
  52.   
  53.             System.out.println("线程" + Thread.currentThread().getName()  
  54.                     + "已发送命令,正在等待结果");  
  55.   
  56.             // 裁判等待各个运动员的结果  
  57.             cdAnswer.await();  
  58.   
  59.             // 裁判公布获得所有运动员的成绩  
  60.             System.out.println("线程" + Thread.currentThread().getName()  
  61.                     + "已收到所有响应结果");  
  62.         } catch (Exception e) {  
  63.             e.printStackTrace();  
  64.         }  
  65.         service.shutdown();  
  66.   
  67.     }  
  68. }  

Exchanger

      

      1、用于实现两个对象之间的数据交换,每个对象在完成一定的事务后想与对方交换数据,第一个先拿出数据的对象将一直等待第二个对象拿着数据

          到来时,彼此才能交换数据。

      2、方法:exchangeV x

          等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。


      3、应用:使用 Exchanger 在线程间交换缓冲区


示例:

       模拟毒品交易情景

[java] view plaincopy
  1. import java.util.concurrent.Exchanger;  
  2. import java.util.concurrent.ExecutorService;  
  3. import java.util.concurrent.Executors;  
  4.   
  5. public class ExchangerTest {  
  6.   
  7.     public static void main(String[] args) {  
  8.         ExecutorService service = Executors.newCachedThreadPool();  
  9.         final Exchanger exchanger = new Exchanger();  
  10.         service.execute(new Runnable(){  
  11.             public void run() {  
  12.                 try {                 
  13.   
  14.                     String data1 = "毒品";  
  15.                     System.out.println("线程" + Thread.currentThread().getName() +   
  16.                     "正在把: " + data1 +"   交易出去");  
  17.                     Thread.sleep((long)(Math.random()*10000));  
  18.                     String data2 = (String)exchanger.exchange(data1);  
  19.                     System.out.println("线程" + Thread.currentThread().getName() +   
  20.                     "换得了: " + data2);  
  21.                 }catch(Exception e){  
  22.                       
  23.                 }  
  24.             }     
  25.         });  
  26.         service.execute(new Runnable(){  
  27.             public void run() {  
  28.                 try {                 
  29.   
  30.                     String data1 = "美金";  
  31.                     System.out.println("线程" + Thread.currentThread().getName() +   
  32.                     "正在把: " + data1 +"   交易出去");  
  33.                     Thread.sleep((long)(Math.random()*10000));                    
  34.                     String data2 = (String)exchanger.exchange(data1);  
  35.                     System.out.println("线程" + Thread.currentThread().getName() +   
  36.                     "换得了: " + data2);  
  37.                 }catch(Exception e){  
  38.                       
  39.                 }                 
  40.             }     
  41.         });       
  42.     }  
  43. }  

ArrayBlockingQueue


       1、一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列包含固定长度的队列和不固定长度的队列。

         这是一个典型的有界缓存区,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能

    再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。


通俗的讲:

      当指定队列大小,如果已经放满,其他存入数据的线程就阻塞,等着该队列中有空位,才能放进去。当取的比较快,队列中没有数据,

      取数据的线程阻塞,等队列中放入了数据,才可以取。

        2、ArrayBlockingQueue中只有puttake方法才具有阻塞功能。方法类型如下


抛出异常

特殊值

阻塞

超时

插入

add(e)

offer(e)

put(e)

offer(e, time, unit)

移除

remove()

poll()

take()

poll(time, unit)

检查

element()

peek()

不可用

不可用


示例:

       用3个空间的队列来演示向阻塞队列中存取数据的效果。

[java] view plaincopy
  1. 通俗的将:  
  2. 当指定队列大小,如果已经放满,其他存入数据的线程就阻塞,等着该队列中有空位,才能放进去。当取的比较快,队列中没有数据,取数据的线程阻塞,等队列中放入了数据,才可以取。  
  3.   
  4.   
  5. ArrayBlockingQueue中只有put和take方法才具有阻塞功能。方法类型如下  
  6.   
  7.     抛出异常    特殊值 阻塞  超时  
  8. 插入  add(e)  offer(e)    put(e)  offer(e, time, unit)  
  9. 移除  remove()    poll()  take()  poll(time, unit)  
  10. 检查  element()   peek()  不可用 不可用  
  11.   
  12. 3个空间的队列来演示向阻塞队列中存取数据的效果。  
  13.   
  14. package cn.xushuai.thread;  
  15. import java.util.concurrent.ArrayBlockingQueue;  
  16. import java.util.concurrent.BlockingQueue;  
  17.   
  18. public class BlockingQueueTest {  
  19.     public static void main(String[] args) {  
  20.         final BlockingQueue queue = new ArrayBlockingQueue(3);  
  21.         for(int i=0;i<2;i++){  
  22.             new Thread(){  
  23.                 public void run(){  
  24.                     while(true){  
  25.                         try {  
  26.                             Thread.sleep((long)(Math.random()*1000));  
  27.                             System.out.println(Thread.currentThread().getName() + "准备放数据!");                              
  28.                             queue.put(1);   //放进去后,可能立即执行“准备取数据”  
  29.                             System.out.println(Thread.currentThread().getName() + "已经放了数据," +                             
  30.                                         "队列目前有" + queue.size() + "个数据");  
  31.                         } catch (InterruptedException e) {  
  32.                             e.printStackTrace();  
  33.                         }  
  34.   
  35.                     }  
  36.                 }  
  37.                   
  38.             }.start();  
  39.         }  
  40.           
  41.         new Thread(){  
  42.             public void run(){  
  43.                 while(true){  
  44.                     try {  
  45.                         //将此处的睡眠时间分别改为100和1000,观察运行结果  
  46.                         Thread.sleep(1000);  
  47.                         System.out.println(Thread.currentThread().getName() + "准备取数据!");  
  48.                         queue.take();   //取出后可能來不及执行下面的打印语句,就跑到了“准备放数据”,  
  49.                         System.out.println(Thread.currentThread().getName() + "已经取走数据," +                             
  50.                                 "队列目前有" + queue.size() + "个数据");                      
  51.                     } catch (InterruptedException e) {  
  52.                         e.printStackTrace();  
  53.                     }  
  54.                 }  
  55.             }  
  56.         }.start();            
  57.     }  
  58. }  

阻塞队列间的通信

           A队列向空间中存数据,B从空间里取数据,A存入后,通知B去取,B取过之后,通知A去放,依次循环


示例:

        子线程先循环10次,接着主线程循环100次,接着又回到子线程,循环10次,再回到主线程又循环100,如此循环50次。

        

说明:

       这里通过使 用两个具有1个空间的队列来实现同步通知的功能(实现了锁和condition的功能),以便实现队列间的通信,其中使用到了

       构造代码块为主队列先存入一个数据,以使其先阻塞,子队列先执行。


使用构造代码块的原因:

       成员变量在创建类的实例对象时,才分配空间,才能有值,所以创建一个构造方法来给main_quene赋值,这里不可以使用静态代码块,因为静态

       在还没创建对象就存在, 而sub_quene和main_quene是对象创建以后的成员变量,所以这里用匿名构造方法,它的运行时期在任何构造方法之前,

       创建几个对象就执行几次

[java] view plaincopy
  1. import java.util.concurrent.ArrayBlockingQueue;  
  2. import java.util.concurrent.BlockingQueue;  
  3.   
  4. public class BlockingQueueCommunication {  
  5.     public static void main(String[] args){  
  6.           
  7.         final Business business = new Business();  
  8.           
  9.         new Thread(new Runnable(){  
  10.             @Override  
  11.             public void run() {  
  12.                 for(int i=1;i<=50;i++){  
  13.                     business.sub(i);  
  14.                 }  
  15.             }  
  16.         }).start();  
  17.   
  18.             //主线程外部循环  
  19.             for(int i=1;i<=50;i++){  
  20.                 business.main(i);  
  21.           }  
  22.        }  
  23.   
  24.     //业务类  
  25.     static class Business{  
  26.           
  27.         BlockingQueue<Integer> sub_quene = new ArrayBlockingQueue<Integer>(1);  
  28.         BlockingQueue<Integer> main_quene = new ArrayBlockingQueue<Integer>(1);  
  29.                   
  30.         {  
  31.   //为了让子队列先走,所以在一开始就往主队列中存入一个对象,使其阻塞。  
  32.             try {  
  33.                 main_quene.put(1);    
  34.             } catch (InterruptedException e) {  
  35.                 e.printStackTrace();  
  36.             }         
  37.         }  
  38.           
  39.         //子队列先走       
  40.         public  void sub(int i){  
  41.               
  42.             try {  
  43.                 sub_quene.put(1);   //子队列第一次存入,可以执行,但由于只有1个空间,已经存满,所以只有在执行后要等到take之后才能继续下次执行  
  44.             } catch (InterruptedException e) {  
  45.                 e.printStackTrace();  
  46.             }  
  47.             //子队列循环执行  
  48.             for(int j=1;j<=10;j++){  
  49.                 System.out.println("sub thread sequence of"+i+",loop of "+j);  
  50.             }  
  51.             try {  
  52.                 main_quene.take();  //让主队列从已经填满的队列中取出数据,使其开始第一次执行  
  53.             } catch (InterruptedException e) {  
  54.                 e.printStackTrace();  
  55.             }  
  56.         }  
  57.           
  58.         public void main(int i){  
  59.               
  60.             try {  
  61.                 main_quene.put(1);  //主队列先前放过1个空间,现在处于阻塞状态,等待子队列通知,即子线程中的main_quene.take();   
  62.             } catch (InterruptedException e) {  
  63.                 e.printStackTrace();  
  64.             }  
  65.               
  66.   //主队列循环执行  
  67.             for(int j=1;j<=100;j++){  
  68.                 System.out.println("main thread sequence of"+i+", loop of "+j);  
  69.             }     
  70.             try {  
  71.                 sub_quene.take(); //让子队列从已经填满的队列中取出数据,使其执行  
  72.             } catch (InterruptedException e) {  
  73.                 e.printStackTrace();  
  74.             }  
  75.         }     
  76.     }  
  77.    }  


三、Java5中的一些同步集合类:


1、ConcurrentHashMap(同步的HashMap

       支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。

   不过,尽管所有操作都是线程安全的,但获取操作不 必锁定,并且不 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 

   进行互操作,这取决于其线程安全,而与其同步细节无关。


内部原理:

      其实内部使用了代理模式,你给我一个HashMap,我就给你一个同步的HashMap。同步的HashMap在调用方法时,是去分配给原始的HashMap只是在去

   调用方法的同时加上了Synchronized,以此实现同步效果


2、ConcurrentSkipListSet类似于TreeSet

      一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的

   Comparator 进行排序,具体取决于使用的构造方法。


3、CopyOnWriteArrayList 

      ArrayList 的一个线程安全的变体,可解决线程安全问题,在遍历的时候,同时进行添加操作。其中所有可变操作(add、set 等等)都是通过对底层数

      组进行一次新的复制来实现的。


4、CopyOnWriteArraySet

    对其所有操作使用内部 CopyOnWriteArrayList 的 Set     


0 0
原创粉丝点击