java并发编程系列之ReadWriteLock读写锁的使用

前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读与读之间互斥。

ReadWriteLock也是一个接口,原型如下:

 

[java] view plain copy

  1. public interface ReadWriteLock {  
  2.     Lock readLock();  
  3.     Lock writeLock();  
  4. }  

该接口只有两个方法,读锁和写锁。也就是说,我们在写文件的时候,可以将读和写分开,分成2个锁来分配给线程,从而可以做到读和读互不影响,读和写互斥,写和写互斥,提高读写文件的效率。该接口也有一个实现类ReentrantReadWriteLock,下面我们就来学习下这个类。

我们先看一下,多线程同时读取文件时,用synchronized实现的效果,代码如下:

[java] view plain copy

  1. public class ReadAndWriteLock {  
  2.     public synchronized void get(Thread thread) {  
  3.         System.out.println("start time:"+System.currentTimeMillis());  
  4.         for(int i=0; i<5; i++){  
  5.             try {  
  6.                 Thread.sleep(20);  
  7.             } catch (InterruptedException e) {  
  8.                 e.printStackTrace();  
  9.             }  
  10.             System.out.println(thread.getName() + ":正在进行读操作……");  
  11.         }  
  12.         System.out.println(thread.getName() + ":读操作完毕!");  
  13.         System.out.println("end time:"+System.currentTimeMillis());  
  14.     }  
  15.       
  16.     public static void main(String[] args) {  
  17.         final ReadAndWriteLock lock = new ReadAndWriteLock();  
  18.         new Thread(new Runnable() {  
  19.             @Override  
  20.             public void run() {  
  21.                 lock.get(Thread.currentThread());  
  22.             }  
  23.         }).start();  
  24.           
  25.         new Thread(new Runnable() {  
  26.             @Override  
  27.             public void run() {  
  28.                 lock.get(Thread.currentThread());  
  29.             }  
  30.         }).start();  
  31.     }  
  32. }  

测试结果如下:

[java] view plain copy

  1. start time:1442459467623  
  2. Thread-0:正在进行读操作……  
  3. Thread-0:正在进行读操作……  
  4. Thread-0:正在进行读操作……  
  5. Thread-0:正在进行读操作……  
  6. Thread-0:正在进行读操作……  
  7. Thread-0:读操作完毕!  
  8. end time:1442459467723  
  9. start time:1442459467723  
  10. Thread-1:正在进行读操作……  
  11. Thread-1:正在进行读操作……  
  12. Thread-1:正在进行读操作……  
  13. Thread-1:正在进行读操作……  
  14. Thread-1:正在进行读操作……  
  15. Thread-1:读操作完毕!  
  16. end time:1442459467823  

整个过程耗时200ms

我们可以看到,及时是在读取文件,在加了synchronized关键字之后,读与读之间,也是互斥的,也就是说,必须等待Thread-0读完之后,才会轮到Thread-1线程读,而无法做到同时读文件,这种情况在大量线程同时都需要读文件的时候,读写锁的效率,明显要高于synchronized关键字的实现。下面我们来测试一下,代码如下:

[java] view plain copy

  1. public class ReadAndWriteLock {  
  2.     ReentrantReadWriteLock lock = new ReentrantReadWriteLock();  
  3.     public void get(Thread thread) {  
  4.         lock.readLock().lock();  
  5.         try{  
  6.             System.out.println("start time:"+System.currentTimeMillis());  
  7.             for(int i=0; i<5; i++){  
  8.                 try {  
  9.                     Thread.sleep(20);  
  10.                 } catch (InterruptedException e) {  
  11.                     e.printStackTrace();  
  12.                 }  
  13.                 System.out.println(thread.getName() + ":正在进行读操作……");  
  14.             }  
  15.             System.out.println(thread.getName() + ":读操作完毕!");  
  16.             System.out.println("end time:"+System.currentTimeMillis());  
  17.         }finally{  
  18.             lock.readLock().unlock();  
  19.         }  
  20.     }  
  21.       
  22.     public static void main(String[] args) {  
  23.         final ReadAndWriteLock lock = new ReadAndWriteLock();  
  24.         new Thread(new Runnable() {  
  25.             @Override  
  26.             public void run() {  
  27.                 lock.get(Thread.currentThread());  
  28.             }  
  29.         }).start();  
  30.           
  31.         new Thread(new Runnable() {  
  32.             @Override  
  33.             public void run() {  
  34.                 lock.get(Thread.currentThread());  
  35.             }  
  36.         }).start();  
  37.     }  
  38. }  

测试结果如下:

[java] view plain copy

  1. start time:1442460030593  
  2. start time:1442460030593  
  3. Thread-1:正在进行读操作……  
  4. Thread-0:正在进行读操作……  
  5. Thread-1:正在进行读操作……  
  6. Thread-0:正在进行读操作……  
  7. Thread-1:正在进行读操作……  
  8. Thread-0:正在进行读操作……  
  9. Thread-1:正在进行读操作……  
  10. Thread-0:正在进行读操作……  
  11. Thread-1:正在进行读操作……  
  12. Thread-0:正在进行读操作……  
  13. Thread-0:读操作完毕!  
  14. Thread-1:读操作完毕!  
  15. end time:1442460030693  
  16. end time:1442460030693  

整个过程耗时:100ms

从测试结果来看,Thread-0和Thread-1是在同时读取文件。

通过两次实验的对比,我们可以看出来,读写锁的效率明显高于synchronized关键字

不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。读锁和写锁是互斥的。

下面我们来验证下读写锁的互斥关系,代码如下:

[java] view plain copy

  1. public class ReadAndWriteLock {  
  2.    ReentrantReadWriteLock lock = new ReentrantReadWriteLock();  
  3.     public static void main(String[] args) {  
  4.         final ReadAndWriteLock lock = new ReadAndWriteLock();  
  5.     // 建N个线程,同时读  
  6.         ExecutorService service = Executors.newCachedThreadPool();  
  7.         service.execute(new Runnable() {  
  8.             @Override  
  9.             public void run() {  
  10.                 lock.readFile(Thread.currentThread());  
  11.             }  
  12.         });  
  13.         // 建N个线程,同时写  
  14.         ExecutorService service1 = Executors.newCachedThreadPool();  
  15.         service1.execute(new Runnable() {  
  16.             @Override  
  17.             public void run() {  
  18.                 lock.writeFile(Thread.currentThread());  
  19.             }  
  20.         });  
  21.     }  
  22.     // 读操作  
  23.     public void readFile(Thread thread){  
  24.         lock.readLock().lock();  
  25.         boolean readLock = lock.isWriteLocked();  
  26.         if(!readLock){  
  27.             System.out.println("当前为读锁!");  
  28.         }  
  29.         try{  
  30.             for(int i=0; i<5; i++){  
  31.                 try {  
  32.                     Thread.sleep(20);  
  33.                 } catch (InterruptedException e) {  
  34.                     e.printStackTrace();  
  35.                 }  
  36.                 System.out.println(thread.getName() + ":正在进行读操作……");  
  37.             }  
  38.             System.out.println(thread.getName() + ":读操作完毕!");  
  39.         }finally{  
  40.          System.out.println("释放读锁!");  
  41.             lock.readLock().unlock();  
  42.         }  
  43.     }  
  44.     // 写操作  
  45.     public void writeFile(Thread thread){  
  46.         lock.writeLock().lock();  
  47.         boolean writeLock = lock.isWriteLocked();  
  48.         if(writeLock){  
  49.             System.out.println("当前为写锁!");  
  50.         }  
  51.         try{  
  52.             for(int i=0; i<5; i++){  
  53.                 try {  
  54.                     Thread.sleep(20);  
  55.                 } catch (InterruptedException e) {  
  56.                     e.printStackTrace();  
  57.                 }  
  58.                 System.out.println(thread.getName() + ":正在进行写操作……");  
  59.             }  
  60.             System.out.println(thread.getName() + ":写操作完毕!");  
  61.         }finally{  
  62.          System.out.println("释放写锁!");  
  63.             lock.writeLock().unlock();  
  64.         }  
  65.     }  
  66. }  

测试结果如下:

[java] view plain copy

  1. // 读锁和读锁测试结果:  
  2. 当前为读锁!  
  3. 当前为读锁!  
  4. pool-2-thread-1:正在进行读操作……  
  5. pool-1-thread-1:正在进行读操作……  
  6. pool-2-thread-1:正在进行读操作……  
  7. pool-1-thread-1:正在进行读操作……  
  8. pool-2-thread-1:正在进行读操作……  
  9. pool-1-thread-1:正在进行读操作……  
  10. pool-2-thread-1:正在进行读操作……  
  11. pool-1-thread-1:正在进行读操作……  
  12. pool-1-thread-1:正在进行读操作……  
  13. pool-2-thread-1:正在进行读操作……  
  14. pool-1-thread-1:读操作完毕!  
  15. pool-2-thread-1:读操作完毕!  
  16. 释放读锁!  
  17. 释放读锁!  
  18. // 测试结果不互斥  
  19.   
  20. // 读锁和写锁,测试结果如下:  
  21. 当前为读锁!  
  22. pool-1-thread-1:正在进行读操作……  
  23. pool-1-thread-1:正在进行读操作……  
  24. pool-1-thread-1:正在进行读操作……  
  25. pool-1-thread-1:正在进行读操作……  
  26. pool-1-thread-1:正在进行读操作……  
  27. pool-1-thread-1:读操作完毕!  
  28. 释放读锁!  
  29. 当前为写锁!  
  30. pool-2-thread-1:正在进行写操作……  
  31. pool-2-thread-1:正在进行写操作……  
  32. pool-2-thread-1:正在进行写操作……  
  33. pool-2-thread-1:正在进行写操作……  
  34. pool-2-thread-1:正在进行写操作……  
  35. pool-2-thread-1:写操作完毕!  
  36. 释放写锁!  
  37. // 测试结果互斥  
  38.   
  39. // 写锁和写锁,测试结果如下:  
  40. 当前为写锁!  
  41. pool-1-thread-1:正在进行写操作……  
  42. pool-1-thread-1:正在进行写操作……  
  43. pool-1-thread-1:正在进行写操作……  
  44. pool-1-thread-1:正在进行写操作……  
  45. pool-1-thread-1:正在进行写操作……  
  46. pool-1-thread-1:写操作完毕!  
  47. 释放写锁!  
  48. 当前为写锁!  
  49. pool-2-thread-1:正在进行写操作……  
  50. pool-2-thread-1:正在进行写操作……  
  51. pool-2-thread-1:正在进行写操作……  
  52. pool-2-thread-1:正在进行写操作……  
  53. pool-2-thread-1:正在进行写操作……  
  54. pool-2-thread-1:写操作完毕!  
  55. 释放写锁!  
  56. // 测试结果互斥  
  57. 备注:将上面的代码修改为建立N个线程,都是写,写场景,或者都是读,读场景,或者是读,写场景就可以得到上面的结果

关于锁的内容我们就讲到这里,下面对锁的相关概念做一个介绍:

1.可重入(Reentrant)锁

如果锁具备可重入性,则称作为可重入锁。像synchronized和 ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一 个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法 method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。

看下面这段代码就明白了:

[java] view plain copy

  1. classMyClass {  
  2.     public synchronized void method1() {  
  3.         method2();  
  4.     }  
  5.     public synchronized void method2() {  
  6.     }  
  7. }  

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程 A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请 锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。  而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

2.可中断锁

  可中断锁:顾名思义,就是可以相应中断的锁。

  在Java中,synchronized就不是可中断锁,而Lock是可中断锁。

  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。

  3.公平锁

  公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。

  非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。

  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。

而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。设置方法如下:ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);

4、读写锁

前面已经介绍,这里不做赘述

转载于:https://blog.csdn.net/liuchuanhong1/article/details/53539341

总结:ReadWriteLock 当写操作时,其他线程无法读取或者写入数据,而当读操作的时候,其它线程无法写入数据,但是却可以读取数据。故ReadWriteLock使用的场景是读多写少的场景

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值