JUC锁: ReentrantReadWriteLock详解
ReentrantReadWriteLock源码分析
类的继承关系
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable
可以看到,ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口只是定义了读锁和写锁的方法体,具体实现需要到子类中去定义。同时还实现了序列化接口。
类的内部类
ReentrantReadWriteLock源码中,共定义了5个内部类,在Sync、NonfairSync、FairSync的基础上。还定义了ReadLock和WriteLock两个内部类。
好,我们逐步分析内部类。
Sync内部类
abstract static class Sync extends AbstractQueuedSynchronizer
Sync继承AQS。
在内部类Sync中,还包括两个内部类。
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
首先先分析HoldCounter类
HolderCounter主要与读锁配套使用,其中包含两个属性
- count :用于记录读线程重入的次数
- tid:用于唯一标识一个线程。
接下来分析ThreadLocalHoldCounter
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程和对象相关联在没有set的情况下,get到的均是initialValue方法中生成的那个HolderCounter对象。
Sync类的属性
版本号
private static final long serialVersionUID = 6317671515068378041L;
高16位为读锁、低16位为写锁
static final int SHARED_SHIFT = 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;
本地线程计数器
private transient ThreadLocalHoldCounter readHolds;
缓存的计数器
private transient HoldCounter cachedHoldCounter;
第一个读线程
private transient Thread firstReader = null;
第一个读线程的计数
private transient int firstReaderHoldCount;
Sync类的构造方法
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
在构造函数中设置本地线程计数器和AQS的State。
Sync核心函数分析
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
此函数可以获得读锁的线程数量,因为在ReentrantReadWriteLock中,高16位表示读锁、低16位表示写锁。所以只需要右移16位,那么就可以获得读锁的线程数量。
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
表示写锁的线程数量。
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively()) //判断是否为独占线程
throw new IllegalMonitorStateException(); 如果不是独占线程,则直接抛出异常
int nextc = getState() - releases; // 计算释放后的写锁的数量
boolean free = exclusiveCount(nextc) == 0; //是否释放成功
if (free)
setExclusiveOwnerThread(null); // 如果释放成功 则将独占线程设置为空
setState(nextc); // 设置状态
return free;
}
此函数用于释放写锁资源,首先会判断该线程是不是独占线程,如果不是独占线程则抛出异常。如果是独占线程,此时写锁资源可以释放,计算释放后的写锁的数量,如果为0,说明成功释放。当前资源不被占用,否则继续占用。
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
Thread current = Thread.currentThread(); //获取当前线程
int c = getState(); //获取状态
int w = exclusiveCount(c); //获取写线程的数量
if (c != 0) { //状态不为0
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread()) //写线程的数量为0,或者当前线程没有独自占用资源
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT) //如果写线程占用的线程数超过了规定的最大线程数
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires); //设置AQS 状态
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires)) //写线程是否应该被阻塞
return false;
//设置独占线程
setExclusiveOwnerThread(current);
return true;
}
这个函数的作用是获取写锁,那么首先会获取当前线程和状态值,如果状态值为0,说明此时共享资源上既没有写锁也没有读锁。此时需要判断当前写锁是否需要阻塞,如果是非公平的情况下,则不需要阻塞,直接竞争上岗,如果公平方式,则需要检查当前线程节点在CLH队列中是否有前驱结点,也就是等待时间更长的线程节点,如果存在则阻塞当前线程。
如果状态值不为0,则表示此时存在读锁或者写锁在占用资源。如果写线程数量为0或者当前线程没有独占资源,则返回false。如果写线程的数量超过了最大线程数的规定,也返回false。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread(); //获取当前线程
if (firstReader == current) { //当前线程是第一个读线程
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1) //读线程占用的资源数为1
firstReader = null;
else
firstReaderHoldCount--;
} else {
//当前线程不是第一个读线程
HoldCounter rh = cachedHoldCounter;
//计数器为空或者tid并不是当前运行线程的pid
if (rh == null || rh.tid != getThreadId(current))
//获取当前线程对应的计数器
rh = readHolds.get();
//获取计数
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
//死循环的方式获取状态
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
此函数用于读锁释放锁。具体有些没看懂 ,不做总结了。
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
* 2. Otherwise, this thread is eligible for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
//获取当前线程
Thread current = Thread.currentThread();
//获取状态
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current) //写线程数为0并且占有资源的不是当前线程
return -1;
//获取读锁的数量
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) { //如果读锁不被阻塞、读线程数小于最大值,CAS成功
if (r == 0) {
//如果读锁数量为0 则设置当前线程为第一个读线程
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
//如果当前线程已经是读线程了 则占用资源数加1
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
以上是Sync主要的内部方法,主要定义了读锁的获取与释放、写锁的获取与释放。
其他内部类的操作实际上就是对Sync进行操作,二次封装而已。
ReentrantReadWriteLock的属性
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
// 版本序列号
private static final long serialVersionUID = -6992448646407690164L;
// 读锁
private final ReentrantReadWriteLock.ReadLock readerLock;
// 写锁
private final ReentrantReadWriteLock.WriteLock writerLock;
// 同步队列
final Sync sync;
private static final sun.misc.Unsafe UNSAFE;
// 线程ID的偏移地址
private static final long TID_OFFSET;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> tk = Thread.class;
// 获取线程的tid字段的内存地址
TID_OFFSET = UNSAFE.objectFieldOffset
(tk.getDeclaredField("tid"));
} catch (Exception e) {
throw new Error(e);
}
}
}
ReentrantReadWriteLock中包含了读锁、写锁、还有Sync同步队列。
ReentrantReadWriteLock的构造函数
public ReentrantReadWriteLock() {
this(false);
}
public ReentrantReadWriteLock(boolean fair) {
// 公平策略或者是非公平策略
sync = fair ? new FairSync() : new NonfairSync();
// 读锁
readerLock = new ReadLock(this);
// 写锁
writerLock = new WriteLock(this);
}
默认也是非公平的策略,并且还在构造函数中生成了ReadLock、WriteLock对象。
对于ReentrantReadWriteLock的核心函数,其内部都是调用的Sync的函数,这一点跟ReentrantLock一样。
锁降级
锁降级指的是写锁降为读锁的过程。如果写锁释放,然后获取读锁,这种得分段释放获取的方式不能成为锁降级。锁降级:把持住当前的写锁,尝试获取读锁,然后释放写锁。
作用:
锁降级的作用是:如果当前线程获取了写锁,然后将写锁释放,而此刻另一个线程获取了写锁直接修改了数据,那么当前线程是无法感知到数据的更新的。但是如果写锁不释放直接获取读锁,也就是进行锁降级,那么这是当前线程因为拥有了读锁,会阻塞其他不安全的写锁线程,那么当前线程就安全的获取了数据。
总结
- 读写锁并不完全是排他锁,因为对于读写锁而言,在同一时刻可以允许多个读线程同时访问,但是当写线程访问得时候,所有的读线程和其他的写线程都必须阻塞。
- 读写锁使用同步队列来维护读写各自的状态,也就是高16位表示读状态,低十六位表示写状态。
- 写锁是支持重新进入的排他锁,如果当前线程已经获取了写锁,重新进入的时候,写状态加1。否则如果当前线程在获取写锁的时候,读锁已经获取了也就是说读锁状态值不为0,或者有线程获取了写锁,但是这个线程并不是当前线程,此时当前线程的操作被阻塞。
- 读锁是支持重新进入的共享锁,如果当前线程已经获得读锁,重新进入的时候,读锁状态值加1。如果读锁在重新进入的时候,发现已经有写锁在访问了,此时当前线程的读锁操作将被阻塞。
不完全是排他锁,因为对于读写锁而言,在同一时刻可以允许多个读线程同时访问,但是当写线程访问得时候,所有的读线程和其他的写线程都必须阻塞。 - 读写锁使用同步队列来维护读写各自的状态,也就是高16位表示读状态,低十六位表示写状态。
- 写锁是支持重新进入的排他锁,如果当前线程已经获取了写锁,重新进入的时候,写状态加1。否则如果当前线程在获取写锁的时候,读锁已经获取了也就是说读锁状态值不为0,或者有线程获取了写锁,但是这个线程并不是当前线程,此时当前线程的操作被阻塞。
- 读锁是支持重新进入的共享锁,如果当前线程已经获得读锁,重新进入的时候,读锁状态值加1。如果读锁在重新进入的时候,发现已经有写锁在访问了,此时当前线程的读锁操作将被阻塞。
- 读锁可重入读锁,读锁之后不能重入写锁。写锁可重入写锁和读锁。写锁之后重入读锁是锁降级。