多线程并发库高级应用 之 java5中的线程并发库--线程锁技术

转自:http://blog.csdn.net/xushuaic/article/details/8561859

笔记摘要:

      这里介绍了java5中的线程锁技术:Lock和Condition,实现线程间的通信,其中的读锁和写锁的使用通过一个缓存系统进行了演示,对于Condition的应用通过

      一个阻塞队列进行演示。



线程锁技术:Lock & Condition 实现线程同步通信

          所属包:Java.util.concurrent.locks


一、Lock


      1Lock比传统线程模型中的synchronized方式更加面向对象,相对于synchronized 方法和语句它具有更广泛的锁定操作,此实现允许更灵活的结构,

            可以具有差别很大的属性,可以支持多个相关的 Condition 对象。 


      2于现实生活中类似,锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的结果,它们必须用同一个Lock对象,锁是上在代表要操作的

      资源的类的内部方法中,而不是线程代码中。


      3读写锁:

        分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM自己控制的。


      4读写锁的使用情景:

        如果代码只读数据,就可以很多人共同读取,但不能同时写。

        如果代码修改数据,只能有一个人在写,且不能同时读数据。


APIReentrantReadWriteLock类提供的一个读写锁缓存示例:
[java]  view plain  copy
  1. class CachedData {  
  2.      
  3.   Object data;  
  4.     volatile boolean cacheValid;  
  5.      
  6.   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();  
  7.   
  8.   void processCachedData() {  
  9.        
  10.         rwl.readLock().lock();  
  11.       
  12.         if (!cacheValid) {  
  13.           
  14.         // Must release read lock before acquiring write lock  
  15.           
  16.         rwl.readLock().unlock();  
  17.         rwl.writeLock().lock();  
  18.           
  19.        // Recheck state because another thread might have acquired  
  20.        // write lock and changed state before we did.    
  21.       if (!cacheValid) {  
  22.             
  23.            data = ...       
  24.            cacheValid = true;  
  25.           
  26.        }  
  27.       // Downgrade by acquiring read lock before releasing write lock  
  28.              rwl.readLock().lock();  
  29.           
  30.         rwl.writeLock().unlock(); // Unlock write, still hold read  
  31.        
  32.       }  
  33.          use(data);  
  34.         rwl.readLock().unlock();  
  35.      
  36.   }  
  37.    
  38. }  


读写锁的应用:编写一个缓存系统


注解:

         为了避免线程的安全问题,synchronizedReadWriteLock都可以,synchronized也防止了并发读取,性能较低

        有一个线程先进去,开始读取数据,进行判断,发现没有数据,其他线程就没有必要进去了,就释放读锁,加上写锁,

        去查找数据写入,为了避免写入的其他对象等待,再做一次判断,数据写入完成后,释放写锁,上读锁,防止写入,

        还原原来的状态。

        两次判断:第一次为了写入数据,所以释放读锁,上写锁。第二次为了防止阻塞的线程重复写入

*/

[java]  view plain  copy
  1. import java.util.HashMap;  
  2. import java.util.Map;  
  3. import java.util.concurrent.locks.ReadWriteLock;  
  4. import java.util.concurrent.locks.ReentrantReadWriteLock;  
  5.   
  6. public class CacheDemo {  
  7.   
  8.     //定义一个map用于缓存对象  
  9.     private Map<String, Object> cache = new HashMap<String, Object>();  
  10.           
  11.     //获取一个读写锁对象  
  12.     private ReadWriteLock rwl = new ReentrantReadWriteLock();  
  13.       
  14.     //带有缓存的获取指定值的方法  
  15.     public  Object getData(String key){  
  16.         rwl.readLock().lock();      //上读锁  
  17.         Object value = null;  
  18.         try{  
  19.             value = cache.get(key); //获取要查询的值     
  20.             if(value == null){  //线程出现安全问题的地方  
  21.                   
  22.                 rwl.readLock().unlock();    //没有数据,释放读锁,上写锁  
  23.                 rwl.writeLock().lock(); //多个线程去上写锁,第一个上成功后,其他线程阻塞,第一个线程开始执行下面的代码,最后  
  24.                             //释放写锁后,后面的线程继续上写锁,为了避免后面的线程重复写入,进行二次判断  
  25.   
  26.                 try{  
  27.                     if(value==null){    //二次判断,防止其他线程重复写数据  
  28.                             value = "aaaa"//实际是去查询数据库  
  29.                     }  
  30.                 }finally{  
  31.                     rwl.writeLock().unlock();   //写完数据,释放写锁  
  32.                 }  
  33.                 rwl.readLock().lock();  //恢复读锁  
  34.             }  
  35.         }finally{  
  36.             rwl.readLock().unlock();    //最终释放读锁  
  37.         }  
  38.         return value;   //返回获取到的值  
  39.     }  
  40. }  


二、Condition


     1Condition 将 Object 监视器方法(waitnotify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象

          提供多个等待 setwait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法waitnotify的使用。

     

     2一个锁内部可以有多个Condition,即有多路等待通知,传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须

     嵌套使用多个同步监视器对象。使用一个监视器往往会产生顾此失彼的情况。

     

     3、在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该

        总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,

        因此总是在一个循环中等待。 


Condition的应用:阻塞队列(使用了两个监视器)

 

说明:

      该应用是 java.util.concurrent.locks包中Condition接口中的示例代码。

      使用了两个Condition分别用于管理取数据的线程,和存数据的线程,这样就可以明确的唤醒需要的一类线程,如果使用一个Condition,当队列满了之后,

      唤醒的并不一定就是取数据的线程


[java]  view plain  copy
  1. class BoundedBuffer {  
  2.   
  3.   final Lock lock = new ReentrantLock();  
  4.   final Condition notFull  = lock.newCondition();   
  5.   final Condition notEmpty = lock.newCondition();   
  6.   
  7.   final Object[] items = new Object[100];  
  8.   int putptr, takeptr, count;  
  9.   
  10.   public void put(Object x) throws InterruptedException {  
  11.     lock.lock();  
  12.     try {  
  13.       while (count == items.length) //循环判断队列是否已存满  
  14.         notFull.await();    //如果队列存满了,则要存入数据的线程等待  
  15.       items[putptr] = x;   
  16.       if (++putptr == items.length) putptr = 0;//当队列放满,指针回到0  
  17.       ++count;      //添加了一个数据  
  18.       notEmpty.signal();    //队列中有数据了,所以就唤醒取数据的线程  
  19.     } finally {  
  20.       lock.unlock();  
  21.     }  
  22.   }  
  23.   
  24.   public Object take() throws InterruptedException {  
  25.     lock.lock();  
  26.     try {  
  27.       while (count == 0)    //循环判断,队列是否有空位  
  28.         notEmpty.await();   //要取的线程等待  
  29.       Object x = items[takeptr];   
  30.       if (++takeptr == items.length) takeptr = 0;  
  31.       --count;  //取走一个,说明队列有空闲的位置,  
  32.       notFull.signal(); //所以通知存入的线程  
  33.       return x;  
  34.     } finally {  
  35.       lock.unlock();  
  36.     }  
  37.   }   
  38. }  


Condition练习:

      一共有3个线程,两个子线程先后循环10次,接着主线程循环100次,接着又回到两 个子线程先后循环10次,再回到主线程又循环100,如此循环50次。


思路:

     老二先执行,执行完唤醒老三,老三执行完唤醒老大,老大执行完唤醒老二,以此循环,

     所以定义3Condition对象和一个执行标识即可


示例出现的问题:两个文件中有同名类的情况

解决方案:

          可以将一个文件中的那个同名外部类放进类中,但是静态不能创建内部类的实例对象,所以需要加上static,这样两个类的名称就不一样了。

          一个是原来的类名,一个是在自己类名前面加上外部类的类名。


[java]  view plain  copy
  1. import java.util.concurrent.locks.Condition;  
  2. import java.util.concurrent.locks.Lock;  
  3. import java.util.concurrent.locks.ReentrantLock;  
  4.   
  5. public class ThreeConditionCommunication {  
  6.     public static void main(String[] args){  
  7.           
  8.         final Business business = new Business();  
  9.           
  10.   //创建并启动子线程老二  
  11.         new Thread(new Runnable(){  
  12.             @Override  
  13.             public void run() {  
  14.                 for(int i=1;i<=50;i++){  
  15.                     business.sub2(i);  
  16.                 }  
  17.             }  
  18.         }).start();  
  19.                   
  20.   //创建并启动子线程老三  
  21.         new Thread(new Runnable(){  
  22.             @Override  
  23.             public void run() {  
  24.                 for(int i=1;i<=50;i++){  
  25.                     business.sub3(i);  
  26.                 }  
  27.             }  
  28.         }).start();  
  29.   
  30.             //主线程  
  31.             for(int i=1;i<=50;i++){  
  32.                 business.main(i);  
  33.           }  
  34.        }  
  35.       
  36.     static class Business{  
  37.           
  38.         Lock lock = new ReentrantLock();   
  39.         Condition condition1 = lock.newCondition();  
  40.         Condition condition2 = lock.newCondition();  
  41.         Condition condition3 = lock.newCondition();  
  42.           
  43.         //定义一个变量来决定线程的执行权  
  44.         private int ShouldSub = 1;  
  45.           
  46.         public  void  sub2(int i){  
  47.             //上锁,不让其他线程执行  
  48.             lock.lock();  
  49.             try{  
  50.             if(ShouldSub != 2){ //如果不该老二执行,就等待  
  51.                 try {  
  52.                     condition2.await();  
  53.                 } catch (InterruptedException e) {  
  54.                     e.printStackTrace();  
  55.                 }  
  56.             }  
  57.               
  58.             for(int j=1;j<=10;j++){  
  59.                 System.out.println("sub thread sequence of"+i+",loop of "+j);  
  60.             }  
  61.             ShouldSub = 3;  //准备让老三执行  
  62.             condition3.signal();        //唤醒老三  
  63.             }finally{  
  64.                 lock.unlock();  
  65.             }     
  66.         }  
  67.           
  68.         public  void  sub3(int i){  
  69.               
  70.             lock.lock();  
  71.             try{  
  72.             if(ShouldSub != 3){  
  73.                 try {  
  74.                     condition3.await();  
  75.                 } catch (InterruptedException e) {  
  76.                     e.printStackTrace();  
  77.                 }  
  78.             }  
  79.               
  80.             for(int j=1;j<=10;j++){  
  81.                 System.out.println("sub2 thread sequence of"+i+",loop of "+j);  
  82.             }  
  83.                 ShouldSub = 1;  //准备让老大执行  
  84.                 condition1.signal();        //唤醒老大  
  85.             }finally{  
  86.                 lock.unlock();  
  87.             }  
  88.         }  
  89.           
  90.   //主线程  
  91.         public  void main(int i){  
  92.               
  93.             lock.lock();  
  94.             try{  
  95.             if(ShouldSub!=1){  
  96.                 try {  
  97.                     condition1.await();  
  98.                 } catch (InterruptedException e) {  
  99.                     e.printStackTrace();  
  100.                 }  
  101.             }  
  102.             for(int j=1;j<=100;j++){  
  103.                 System.out.println("main thread sequence of"+i+", loop of "+j);  
  104.             }     
  105.             ShouldSub = 2;  //准备让老二执行  
  106.             condition2.signal();        //唤醒老二  
  107.             }finally{  
  108.                 lock.unlock();  
  109.             }     
  110.         }     
  111.     }  
  112.    }  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值