ReentrantReadWriteLock(读写锁)

Stella981
• 阅读 674

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高并发程序设计》 葛一鸣 郭超 编著:

点赞
收藏
评论区
推荐文章
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java 里面 的锁
A、乐观锁、悲观锁B、偏向锁、轻量级锁、重量级锁C、互斥锁、自旋锁、适应性自旋D、可重入锁、读写锁E、公平锁、非公平锁F、总线锁、缓存锁(linux操作系统底层,由CPU提供的锁)G、锁优化:减少锁持有时间、减小锁粒度、锁分离、锁粗化、锁消除信号量与互斥量:信号
Wesley13 Wesley13
3年前
java编发编程之:CuncurrentHashMap
CuncurrentHashMap通过分析Hashtable就知道,synchronized是针对整张Hash表的,即每次锁住整张表让线程独占,ConcurrentHashMap允许多个修改操作并发进行,其关键在于使用了锁分离技术。它使用了多个锁来控制对hash表的不同部分进行的修改。ConcurrentHashMap
Wesley13 Wesley13
3年前
Java并发系列4
今天讲另一个并发工具,叫读写锁。读写锁是一种分离锁,是锁应用中的一种优化手段。考虑读多写少的情况,这时如果我们用synchronized或ReentrantLock直接修饰读/写方法未尝不可,如:publicstaticclassRw{privateintval;publicsynchr
Wesley13 Wesley13
3年前
JAVA中 ReentrantReadWriteLock读写锁详系教程,包会
一、读写锁简介现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。 针对这种场景,JAVA的并发包提供了读写锁ReentrantReadW
Wesley13 Wesley13
3年前
Java中的读写锁ReadWriteLock
ReadWriteLock是JDK中的读写锁接口ReentrantReadWriteLock是ReadWriteLock的一种实现读写锁非常适合读多写少的场景。读写锁与互斥锁的一个重要区别是读写锁允许多个线程同时读共享变量,这是读写锁在读多写少的情况下性能较高的原因。读写锁的原则:多个线程可同时读共享变量只允许一
Wesley13 Wesley13
3年前
MySQL锁
<br1\.表锁表锁分为写锁,读锁,二者读读不阻塞,读写阻塞,写写阻塞<br<br2\.行锁行锁分为共享锁,排他锁,即读锁和写锁多粒度锁机制自动实现表、行锁共存,InnoDB内部有意向表锁意向共享锁(IS):事务在给一个数据行加共享锁前必须先取得该表的IS锁。
Stella981 Stella981
3年前
ReentrantReadWriteLock实现原理
  在java并发包java.util.concurrent中,除了重入锁ReentrantLock外,读写锁ReentrantReadWriteLock也很常用。在实际开发场景中,在使用共享资源时,可能读操作远远多于写操作。这种情况下,如果对这部分共享资源能够让多个线程读的时候不受阻塞,仅仅在写的时候保证安全性,这样效率会得到显著提升。读写锁Reentra
Stella981 Stella981
3年前
ReentrantReadWriteLock读写锁详解
一、读写锁简介现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。 针对这种场景,JAVA的并发包提供了读写锁ReentrantReadW
Wesley13 Wesley13
3年前
Java并发编程原理与实战十八:读写锁
ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。基本规则:读读不互斥读写互斥写写互斥问题:既然读读不互斥,为何还要加读锁答:如果只是读,是不需要加锁的,加锁本身就有性能上的损耗如果读可以不是最新数据