前言
相比于ReentrantLock 互斥的设计,现实情况是我们更多的碰到的是 读的次数远远大于写的次数。如果在一个读场景远大于写场景的情况下,我们再去使用ReentrantLock 显得浪费资源。这里介绍ReentrantReadWriteLock(读写锁) 就是为了解决这个问题。
1、ReentrantReadWriteLock 用法
ReentrantReadWriteLock 分为读锁和写锁。
所谓的读锁 其实是就是AQS 中的共享模式,允许多个线程同时持有共享锁。而写锁则是AQS 中的独占模式,只有一个线程能够获取锁。
前面文章介绍过AQS ,对了共享和独占模式应该有所了解。
那么这里有一个问题,我们之前说过AQS 中维护了一个state,但是在ReentrantReadWriteLock如何同时保存写锁和读锁的状态的?
答案是 ReentrantReadWriteLock 将state 分成了 高16位 和 低16位。高16代表的是读锁的状态,所以共享锁的线程不会超过 2 << 16,
低16位是独占锁的状态。
(1)读锁和写锁的获取条件
1、写锁 同ReentrantLock 。无其他线程 持有写锁,没有线程持有读锁
2、读锁 没有其他线程持有写锁并且持有读锁的线程不超过 (2<<16) - 1 原因见上面
(2)读写锁的锁降级
因为写锁是独占锁,相比于读锁永远更高的权限。我们认为从写锁到读锁是一种锁的降级。什么情况会发生锁得降级呢,我们来分析一下。
1、读锁要想获取写锁是不可能的,因为写锁是和一切读锁互斥(包括自己拥有的)。
2、写锁可以获取读锁,见上面写锁获取条件
综上,根据读锁和写锁获取的条件 读写锁只能降级 无法升级。下面实验一下
锁升级
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
System.out.println("小米:我这有一个读锁");
lock.writeLock().lock();
System.out.println("小米:我从读锁中拿到一个读锁");
lock.writeLock().unlock();
System.out.println("小米:写锁解开吧!!!");
lock.readLock().unlock();
System.out.println("小米:我也不要读锁了。给你吧");
}
输出结果
image.png
可见在拥有了读锁的情况下,线程时拿不到写锁的。
下面我们读写锁互换位置试试
锁降级
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock();
System.out.println("小米:我从读锁中拿到一个读锁");
lock.readLock().lock();
System.out.println("小米:我这有一个读锁");
lock.writeLock().unlock();
System.out.println("小米:写锁解开吧!!!");
lock.readLock().unlock();
System.out.println("小米:我也不要读锁了。给你吧");
}
输出结果
.
从输出结果来看,先获取写锁再获取读锁是没有问题的。
再看看一个正常使用的例子
public static void main(String[] args) {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
new Thread(() -> {
lock.readLock().lock();
System.out.println("小米:我这有一个读锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("时间过去了三秒");
System.out.println("小米:我不要读锁了。给你吧");
lock.readLock().unlock();
}).start();
new Thread(() -> {
System.out.println("小明:我想要一个写锁");
lock.writeLock().lock();
System.out.println("小明:嘻嘻,我获取了一个写锁");
lock.writeLock().unlock();
}).start();
}
一个线程持有读锁三秒,一个线程等待读锁
输出结果
image.png
到这里ReentrantReadWriteLock 已经结束了,还有什么疑问吗?
网友评论