ReentrantReadWriteLock 源码分析以及 AQS 共享锁 (二)
烟雨星空 人气:1
## 前言
上一篇讲解了 AQS 的独占锁部分(参看:[ReentrantLock 源码分析以及 AQS (一)](https://mp.weixin.qq.com/shttps://img.qb5200.com/download-x/dDjbR76U5C696CXAOAvpng)),这一篇将介绍 AQS 的共享锁,以及基于共享锁实现读写锁分离的 ReentrantReadWriteLock。(若是遇到之前讲过的方法,将不再赘述)
**先思考一下,为什么我们用读写锁分离?**
我们知道 ReentrantLock 用的是独占锁,不管线程是读还是写状态,都会阻塞,这无疑会降低并发量。
但是,我们知道多个线程同时去读数据的时候,并不会产生线程安全的问题,因为它们互不干扰。那么为什么不设计一种方案,让所有的读线程可以共享,一起同时读数据呢,只需要阻塞写的线程就可以了。提高并发的同时,也不会产生数据不一致的现象。
同样的,如果有线程在写数据,那么也会阻塞其它读线程(同样阻塞其它写线程),数据写完之后才可以读数据,这样保证读到的数据都是最新的。
因此,我们可以用读、写两把锁,分别控制数据的读和写。实现读读共享、读写互斥,写写互斥。这也是 ReentrantReadWriteLock 读写分离锁的由来。它非常适合用在读多写少的场景。
## ReentrantReadWriteLock
它和 ReentrantLock 一样,也是一个可重入的锁,并基于 AQS 共享锁实现了读写分离。其内部结构也大同小异,支持公平锁和非公平锁。我们看下它的构造函数,
```
public ReentrantReadWriteLock() {
//默认非公平
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
```
它定义了两个内部类来表示读锁和写锁,并且都通过内部类 Sync 来实现加锁,释放锁等功能。
```
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
...
}
abstract static class Sync extends AbstractQueuedSynchronizer {
}
```
我们再看下公平锁和非公平锁,其中有两个比较重要的方法,用来判断读锁和写锁是否应该被阻塞,后面加锁的时候会用到(其实,实际情况是否真的应该阻塞,还需要斟酌,后面会说)。
```
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
//公平锁的读和写都需要判断,在它前面是否已经有线程在等待。
//有的话,当前线程就需要阻塞,这也体现了公平性。
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
//非公平锁,写的时候不需要阻塞,直接返回false
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
//为了避免写线程饥饿,需要判断同步队列中第一个排队的(head.next)是否是独占锁(写线程)
//如果是的话,当前读线程就需要阻塞,这是 AQS 中的方法
return apparentlyFirstQueuedIsExclusive();
}
}
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
```
**思考:**
我们知道 ReentrantLock 的同步状态和重入次数,是直接用 state 值来表示的。那么,现在我需要读和写两把锁,怎么才能用一个 int 类型的值来表示两把锁的状态呢?并且,锁是可重入的,重入的次数怎么记录呢?
别急,下面一个一个说。
### 怎么用一个 state 值表示读、写两把锁?
![](https://img2020.cnblogs.com/other/1714084/202003/1714084-20200317204510064-1840955138.jpg)
state 是一个 32 位的 int 值,读写锁中,把它一分为二,高 16 位用来表示读状态,其值代表读锁的线程数,如图中为 3 个,低 16位表示写状态,其值代表写锁的重入次数(因为是独占锁)。 这样,就可以分别计算读锁和写锁的个数了。其相关的属性和方法定义在 Sync 类中。
```
static final int SHARED_SHIFT = 16;
//表明读锁每增加一个,state的实际值增加 2^16
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//写锁的最大重入次数,读锁的最大个数
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//持有读锁的线程个数,参数如的 c 代表 state值
//state 的32位二进制位,无符号右移 16位之后,其实就是高16位的值
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//写锁数量,即写锁的重入次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
```
读锁的个数计算比较简单,直接无符号右移 16 位即可。我们看下写锁的重入次数是怎么计算的。先看下 EXCLUSIVE_MASK 这个值,是 (1 << 16) - 1,我们用二进制表示计算过程为:
```
// 1的二进制
0000 0000 0000 0000 0000 0000 0000 0001
// 1左移 16位
0000 0000 0000 0001 0000 0000 0000 0000
//再减 1
0000 0000 0000 0000 1111 1111 1111 1111
//任何一个 32位二进制数 c,和以上值做 “与” 运算都为它本身 c 的低 16 位值
//这个不用解释了吧,这个不会的话,需要好好补充一下基础知识了。。。
```
### 锁的重入次数是怎么计算的?
写锁比较简单,直接用计算出来的低16位值就可以代表写锁的重入次数。
读锁,就比较复杂了,因为高16位只能表示持有共享锁的线程个数,实在是分身乏术啊。所以,在 Sync 内部,维护了一个类,用来表示每个线程重入的次数,
```
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
```
这里边定义了一个计数器来表示重入次数,tid 来表示当前的线程 id 。但是,这样还不够,我们需要把 HoldCounter 和 线程绑定,这样才可以区分出来每个线程分别持有的锁个数(重入次数),这就需要用到 ThreadLocal 了。
```
static final class ThreadLocalHoldCounter
extends ThreadLocal
加载全部内容