ReentrantReadWriteLock(读写锁)

ReentrantReadWriteLock是JDK5中提供的读写分离锁。读写分离锁可以有效的帮助减少锁的竞争,以此来提升系统的性能。用锁分离的机制来提升性能也非常好理解,比如线程A,B,C进行写操作,D,E,F进行读操作,如果使用ReentrantLock或者synchronized关键字,这些线程都是串行执行的,即每次都只有一个线程做操作。但是当D进行读操作时,E,F都需要等待锁,由于读不会对数据的完整性造成破坏,因此这种等待是不合理的。

  在这种情况下,读写锁运行多个线程同时读,是的D,E,F之间真正的并行。但是由于需要考虑数据的完整性,写写操作和读写操作还是需要互相等待和持有锁的,读写锁的约束情况如下:

    ❤ 读-读不互斥:读读之间不阻塞;

    ❤ 读-写互斥:读阻塞写,写也会阻塞读;

    ❤ 写-写互斥:写写阻塞;

源码:

 这是ReentrantReadWriteLock的两个构造函数:

1 public ReentrantReadWriteLock() {
2         this(false);
3     }
4 
5 public ReentrantReadWriteLock(boolean fair) {
6     sync = fair ? new FairSync() : new NonfairSync();
7     readerLock = new ReadLock(this);
8     writerLock = new WriteLock(this);
9   }

  可以看出,读写锁也可以构造公平和非公平锁,默认的是非公平锁。

看下面的例子:

 1 public class ReadWriteLock {
 2     private static Lock lock = new ReentrantLock();
 3     private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
 4     private static Lock readLock = readWriteLock.readLock();
 5     private static Lock writeLock = readWriteLock.writeLock();
 6     private int value;
 7 
 8     //模拟读操作
 9     public Object handleRead(Lock lock) throws InterruptedException{
10         try {
11             lock.lock();
12             System.out.println("获取读锁 :" + System.currentTimeMillis());
13             Thread.sleep(1000);
14             return value;
15         } finally {
16             lock.unlock();
17         }
18     }
19 
20     //模拟写操作
21     public void handleWrite(Lock lock,int index) throws InterruptedException {
22         try {
23             lock.lock();
24             System.out.println("获取写锁:" + System.currentTimeMillis());
25             Thread.sleep(1000);
26             value = index;
27         }finally {
28             lock.unlock();
29         }
30     }
31 
32     //测试
33     public static void main(String[] args){
34         ReadWriteLock demo = new ReadWriteLock();
35         //读线程
36         Runnable readRunnable = new Runnable() {
37             @Override
38             public void run() {
39                 try {
40                     demo.handleRead(readLock);
41                     //demo.handleRead(lock);
42                 } catch (InterruptedException e) {
43                     e.printStackTrace();
44                 }
45             }
46         };
47         //写线程
48         Runnable writeRunnable = new Runnable() {
49             @Override
50             public void run() {
51                 try {
52                     demo.handleWrite(writeLock,new Random().nextInt());
53                     //demo.handleWrite(lock,new Random().nextInt());
54                 } catch (InterruptedException e) {
55                     e.printStackTrace();
56                 }
57             }
58         };
59 
60         for (int i = 0;i < 10;i++){
61             new Thread(readRunnable).start();
62         }
63 
64         for (int i = 0;i < 10;i++){
65             new Thread(writeRunnable).start();
66         }
67 
68     }
69 
70 }

 输出结果:

 1 获取读锁 :1537857549893
 2 获取读锁 :1537857549893
 3 获取读锁 :1537857549893
 4 获取读锁 :1537857549893
 5 获取读锁 :1537857549893
 6 获取读锁 :1537857549893
 7 获取写锁:1537857550893
 8 获取写锁:1537857551893
 9 获取写锁:1537857552893
10 获取写锁:1537857553893
11 获取读锁 :1537857554893
12 获取读锁 :1537857554893
13 获取读锁 :1537857554893
14 获取读锁 :1537857554893
15 获取写锁:1537857555893
16 获取写锁:1537857556893
17 获取写锁:1537857557893
18 获取写锁:1537857558893
19 获取写锁:1537857559893
20 获取写锁:1537857560893

   上述的我们分别让读和写的线程都等待1S,当我们使用读写锁时,可以有输出时间的时间戳看出,整个的过程耗时11秒;也可以从获得读锁的时间戳看出,获取读锁是可以同时获得的,表明读-读不互斥,可以并行的;看输出结果的第6,7行和10,11行和14,15行,可以看出这些获得锁的时间差都为1S,表明了读-写互斥和写-读互斥;再看获取写锁的时间戳,每次获得写锁都是互斥的,每次都间隔1S得到。

  将上述例子,第41和53行代码放开,注释第40和52行代码,执行得到结果:

 1 获取读锁 :1537859337210
 2 获取读锁 :1537859338210
 3 获取读锁 :1537859339210
 4 获取读锁 :1537859340210
 5 获取读锁 :1537859341210
 6 获取读锁 :1537859342210
 7 获取读锁 :1537859343210
 8 获取读锁 :1537859344210
 9 获取读锁 :1537859345210
10 获取读锁 :1537859346210
11 获取写锁:1537859347210
12 获取写锁:1537859348210
13 获取写锁:1537859349210
14 获取写锁:1537859350210
15 获取写锁:1537859351211
16 获取写锁:1537859352211
17 获取写锁:1537859353211
18 获取写锁:1537859354211
19 获取写锁:1537859355211
20 获取写锁:1537859356211

 修改上述代码后,我们是使用重入锁来实现的,可以看出整个过程耗时20秒,再看每次获取锁的时间戳,我们可以得出不论什么线程都是互斥的。

  由两个输出结果可以看出,读写锁对于读比较多的应用场景性能提升较大。

参考:《Java高并发程序设计》 葛一鸣 郭超 编著:

作者:Joe
努力了的才叫梦想,不努力的就是空想,努力并且坚持下去,毕竟这是我相信的力量
原文地址:https://www.cnblogs.com/Joe-Go/p/9699752.html