ReentrantReadWriteLock读写锁类结构图:
AQS如何用一个int值表示读写锁的2种状态?
在AQS的实现类ReentrantReadWriteLock$Sync中,将32位int类型的state分成2部分,高位的16个2进制位表示读锁,低位的16个2进制位表示写锁。即读锁和写锁的并发最多只能达到2的16次方减去1。
0000000000000000 0000000000000000
---------------------------------------------------------------------------------
如果是读锁进入,则变成:
0000000000000001 0000000000000000
即将原有的state值加上65535(即2的16次方-1)
如何获取读锁数量:
将state的高位右移16位,则变成:
0000000000000000 0000000000000001
-------------------------------------------------------------------------------------
如果是写锁进入,则变成:
0000000000000000 0000000000000001
即将原有的state值加1
如何获取写锁数量:
将state高位并上16个0,低位并上16个1(2的16次方减1),0&0或者1始终都为0,即将高位变成0,1&1为1,则低位并上16个1值不变
0000000000000000 0000000000000001
0000000000000000 1111 1111 1111 1111
=0000000000000000 0000000000000001
对应的源码如下:
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;//写锁的最大并发量
//获取读锁数量 将state的高位右移16位
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//获取写锁数量 将state的2进制并上16个1(2的16次方减1)
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
读锁重入实现原理:
ReentrantLock$Sync中的AQS的state表示的是重入次数,因为这个锁是独占锁,只有获取到锁的线程才能执行,才能重入,其他线程只能等待锁释放后去抢占或者排队等候
而ReentrantReadWriteLock$Sync中,AQS的state不在表示重入次数,它表示的是当前有多少读锁和多少写锁,假设没有写锁,那么所有的读锁都能并发访问数据;如果有写锁在排队,则后来的读锁、写锁也需要排队,因为读写互斥、写写互斥
在ReentrantReadWriteLock$Sync中,有一个ThreadLocalHoldCounter类型的属性,该属性是ThreadLocal<HoldCounter>的子类,而HoldCounter类型才是记录线程重入次数的类。具体源码如下:
private transient ThreadLocalHoldCounter readHolds;
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
static final class HoldCounter {
int count = 0;
// Use id, not reference, to avoid garbage retention
final long tid = getThreadId(Thread.currentThread());
}
读锁源码解析:
ReadWriteLock rwlock=new ReentrantReadWriteLock();//不带参数或者参数为false,表示非公平锁
Lock rlock=rwlock.readLock();//获取读锁
new Thread(()->{
rlock.lock();
rlock.unlock();
}).start();
// ReentrantReadWriteLock ReadLock
public void lock() {
sync.acquireShared(1);//调用sync尝试获取读锁
}
//AQS
public final void acquireShared(int arg) {
//tryAcquireShared是一个抽象方法
//尝试获取锁,如果返回值为-1表示没有获取到锁,如果返回值为1表示获取到了锁
if (tryAcquireShared(arg) < 0)
//将当前线程放到队列中
doAcquireShared(arg);
}
//ReentrantReadWriteLock Sycn
/**
* 1.如果写锁被另外一个线程持有,则返回-1
* 2.如果获取到了锁,则返回1
* 3.步骤2失败,则循环重试
**/
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();//获取状态
//如果写锁被另外一个线程持有,则返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
//公平锁时:如果队列为空或者队列第一个节点是当前线程,返回false
// 如果队列队列不为空,则返回true,表示要规规矩矩的排队
//非公平锁时:如果队列为空或者前面没有写锁在排队时,则返回false
// 如果头结点不为空,下一个结点也不为空并且是写锁时,返回true,表示要规规矩矩的排队
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
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);
}
// ReentrantReadWriteLock ReadLock
public void unlock() {
sync.releaseShared(1);
}
//AQS
public final boolean releaseShared(int arg) {
//抽象方法
if (tryReleaseShared(arg)) {//尝试释放读锁,该方法主要是在操纵重入次数,如果次数为0,则返回true
doReleaseShared();
return true;
}
return false;
}
//ReentrantReadWriteLock Sync
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
//如果当前线程是第一个读锁
if (firstReader == current) {
// 如果重入次数为1,表示没有重入过
if (firstReaderHoldCount == 1)
firstReader = null;
else//如果重入过,则重入次数减一
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
//获取到重入次数
int count = rh.count;
if (count <= 1) {
//如果重入次数为1,表示可以真正的释放锁了
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
//如果重入过,则重入次数减一
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
写锁源码解析:
ReadWriteLock rwlock=new ReentrantReadWriteLock();
Lock wlock=rwlock.writeLock();
new Thread(()->{
wlock.lock();
wlock.unlock();
}).start();
//WriteLock
public void lock() {
sync.acquire(1);
}
//AQS
public final void acquire(int arg) {
if (!tryAcquire(arg) &&//尝试获取写锁
//这里调用的是AQS的排队相关方法,因为写锁是互斥的
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//ReentrantReadWriteLock$Sync
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);//获取写锁数量
//如果AQS的state状态不为0,表示队列中有锁,可能是读锁,也可能是写锁
if (c != 0) {
//如果写锁为0 或者当前线程不是排队中写锁的线程,返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//表示当前的写锁是重入进来的,那么返回true
setState(c + acquires);
return true;
}
//表示队列中没有锁,这个写锁是第一个锁
//公平锁时:如果队列为空或者队列第一个节点是当前线程,返回false
// 如果队列队列不为空,则返回true,表示要规规矩矩的排队
//非公平锁:直接返回false
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))//当前锁的CAS释放失败
return false;
setExclusiveOwnerThread(current);
//返回true表示获取到了写锁
return true;
}
总结:
1、读写锁适合读多写少的场景,读读并行,读写、写写互斥
2、读写锁使用一个int类型的state变量,实现了2种锁状态,低16位记录写锁数量,高16位记录读锁数量,而不是记录的重入次数。获取读锁数量时,需要将state的2进制位右移16位;获取写锁数量时,需要将state的2进制位与上前16位0后16位1
3、在读锁或者写锁中,需要记录某个线程的重入次数,使用到了ThreadLocalHoldCounter类,该类是ThreadLocal<HoldCounter>的子类,而HoldCounter类型才是记录线程重入次数的类。
4、不管是读锁还是写锁,在获取锁的时候,都有公平或非公平2种方式,这取决于创建ReentrantReadWriteLock的创建方式
5、读锁加锁过程:
公平锁时:
如果队列不为空且当前线程不是队列的首结点,则表示要规规矩矩的排队,否则执行后面的CAS操作
非公平锁时:
如果头结点不为空,下一个结点是写锁时,表示要规规矩矩的排队;否则执行CAS操作
6、写锁加锁过程:如果队列中有读锁或者写锁时,需要排队,如果队列为空,则直接获取到写锁
公平锁时:
如果队列不为空且当前线程不是队列的第一个结点,则表示要规规矩矩的排队,否则执行后面的CAS操作
非公平锁:
如果队列为空时,直接执行CAS操作