ReentrantReadWriteLock
读写锁,本身也是有AQS实现的,主要分为独占与共享两种状态,分别对应读锁与写锁,读锁是一把共享锁,多个线程都可以共享获取到一把锁。写锁时独占锁,统一时刻只能有一个线程获取到写锁。默认创建的是非公平模式的锁。
我们知道AQS中使用state状态来区分是否获取到锁,在独占模式下如果state == 0说明没有获取到独占锁,state != 0说明有其他线程获取到独占锁。在共享模式下,如果state < 0代表没有获取到共享锁,在CountDownLatch中如果state 不等于 0说明获取不到共享锁,在Semaphore中state小于1获取不到共享锁。
- 独占模式来说,0 代表可获取锁,1 代表锁被别人获取了,重入情况下会往上加
- 共享模式下,每个线程都可以对 state 进行加减操作
state读写状态
在读写锁中将state值分为高16位与敌16位进行设置读写锁的次数
获取写状态:
state&0x0000FFFF:将高16位全部抹去
获取读状态:
state>>>16:无符号补0,右移16位
(3)写状态加1:
state+1
(4)读状态加1:
state +(1<<16)
原则
读读共享、读写互斥、写写互斥
允许写锁降级为读锁,称之为锁降级。
不允许读锁升级升级到写锁,会造成死锁。
demo及源码分析
public static void main(String[] args) {
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
final ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
new Thread(()->{
readLock.lock();
System.out.println("线程1获取到读锁!");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
System.out.println("线程1读锁释放!");
}
},"线程1").start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
readLock.lock();
System.out.println("线程2获取到读锁!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readLock.unlock();
System.out.println("线程2读锁释放!");
}
},"线程2").start();
new Thread(()->{
writeLock.lock();
System.out.println("线程3获取到写锁!");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
writeLock.unlock();
}
},"线程3").start();
}
结果
线程1获取到读锁!
线程2获取到读锁!
线程2读锁释放!
线程1读锁释放!
线程3获取到写锁!
ReentrantReadWriteLock类信息
//这些属性主要时将state分为高低位分别计算,高16位位读锁
//低16位为写锁
static final int SHARED_SHIFT = 16;
//1 00000000 00000000
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
//读锁最多次数
//15个1
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
//用于计算写锁次数
//15个1
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
//读锁次数 向右移动16位,获取高位读锁次数
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
//写锁次数,将高16位置为0,取低位写锁次数
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
//用于计算当前线程的读锁次数与id
static final class HoldCounter {
int count = 0;
final long tid = getThreadId(Thread.currentThread());
}
//ThreadLocal子类,重写默认值方法,就是说没有set时get取的默认值
static final class ThreadLocalHoldCounter
extends ThreadLocal<HoldCounter> {
public HoldCounter initialValue() {
return new HoldCounter();
}
}
//当前线程的ThreadLocal
private transient ThreadLocalHoldCounter readHolds;
//最后一个线程的缓存数据
private transient HoldCounter cachedHoldCounter;
//第一个获取读锁的线程以及次数
private transient Thread firstReader = null;
private transient int firstReaderHoldCount;
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState());
}
读锁
lock
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
//如果获取锁失败将当前线程加入到阻塞队列中并中断当前线程,等待前置节点唤醒
doAcquireShared(arg);
}
tryAcquireShared
尝试获取读锁
protected final int tryAcquireShared(int unused) {
//获取当前线程
Thread current = Thread.currentThread();
//获取状态
int c = getState();
//是否有获取到写锁并且不是当前线程
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
//走当这里说明获取写锁的就是当前线程,就是说当前线程获取到写锁后又获取了读锁,此时是锁降级
//或者没有线程获取到写锁
//获取读锁的次数,走到这里说明可以获取到读锁了,这是对写锁的优化,高优先级
int r = sharedCount(c);
//先判断阻塞队列是否有数据
//如果是公平锁,如果队列有线程正在等待,则当前线程不能跟队列中的线程去争抢
//如果是非公平锁,则需要判断队列的第一个节点是不是写锁,如果是写锁则不能跟他抢锁
//我们考虑非公平锁,如果队列头是一个等待写锁的线程节点,则当前线程不能跟头节点的写锁线程抢占锁。
if (!readerShouldBlock() &&
//读锁此时是否达到最大
r < MAX_COUNT &&
//cas成功,将读锁次数的高16位加一
//就是加1 00000000 0000000,1后面16个0 高16位的1
compareAndSetState(c, c + SHARED_UNIT)) {
//代表当先线程是第一个获取到读锁
if (r == 0) {
//设置第一个线程为当前线程,次数为1
firstReader = current;
firstReaderHoldCount = 1;
//如果跟第一个获取读锁的线程一致,则说明读锁重入,直接加加操作
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//走到这里说明当前线程与第一个获取读锁的线程不一样,我们获取最后获取读锁的线程
HoldCounter rh = cachedHoldCounter;
//如果没有最后一个线程的缓存,或者最后一个线程缓存不等于当前线程
if (rh == null || rh.tid != getThreadId(current))
//当前线程赋值到最后线程缓存
cachedHoldCounter = rh = readHolds.get();
//什么时候rh.count == 0
else if (rh.count == 0)
//记录次数
readHolds.set(rh);
//当前线程获取读锁的次数加加
rh.count++;
}
//返回1 获取到读锁 就是共享锁
return 1;
}
return fullTryAcquireShared(current);
}
- 以上逻辑我们都已经注释
- rh.count什么时候等于0,如果当前线程就是最后一个线程缓存的线程信息,并且已经释放锁后再次获取锁操作,才有可能进入到此处逻辑
fullTryAcquireShared
final int fullTryAcquireShared(Thread current) {
//什么时候进入到这个方法cas失败,如果有两个线程来获取读锁,其中一个cas成功获取到锁,第二个进入到此方法
//如果队列中第一个是写锁,我们也进入到此方法,我们不想与他枪锁,但是我们是来重入读锁的
HoldCounter rh = null;
//死循环 + cas 多线程枪锁
for (;;) {
//获取状态
int c = getState();
//如果有线程获取到写锁,那此时读锁肯定获取不到了,进入到阻塞队列排队
if (exclusiveCount(c) != 0) {
//并且不是当前线程,表明获取读锁失败,加入到队列中排队获取锁
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
//进入到这里说明没有线程获取到写锁
//并且队列中第一个是写锁并正在等待获取锁
if (firstReader == current) {
//直接进入下面的cas
} else {
//处理重入读锁逻辑的
if (rh == null) {
//最后一个线程的缓存赋值给这个
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) {
//将当前线程赋值给最后一个线程缓存
rh = readHolds.get();
//说明上面的刚刚初始化,移除操作
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//cas
if (compareAndSetState(c, c + SHARED_UNIT)) {
//如果读锁 == 0 说明当前是第一个获取读锁的线程
if (sharedCount(c) == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
//下面几段代码就是cachedHoldCounter设置为当前线程
if (rh == null)
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter = rh; // cache for release
}
return 1;
}
}
}
unlock
public void unlock() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
//尝试释放锁
if (tryReleaseShared(arg)) {
//如果尝试释放锁成功
//唤醒后续节点
doReleaseShared();
return true;
}
return false;
}
protected final boolean tryReleaseShared(int unused) {
//获取当前线程
Thread current = Thread.currentThread();
//如果第一个获取读锁的线程等于当前线程
if (firstReader == current) {
//如果第一个获取读锁的线程没有重入,只有一次
if (firstReaderHoldCount == 1)
//把第一个获取读锁线程置空
firstReader = null;
else
//如果第一个获取读锁的线程有重入,则执行减减操作
firstReaderHoldCount--;
} else {
//如果当前线程不是第一个获取读锁的线程
//判断是不是最后一个
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
//获取当前线程获取读锁的次数与线程id
rh = readHolds.get();
int count = rh.count;
//如果次数小于等于1,移除当前线程的数据,次数与id
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
//获取状态
int c = getState();
//读锁减减,高16位减一
int nextc = c - SHARED_UNIT;
//cas成功后
if (compareAndSetState(c, nextc))
//返回读锁是否等于0,读锁是否全部都已经释放
return nextc == 0;
}
}
写锁
写锁比较简单
public void lock() {
sync.acquire(1);
}
public final void acquire(int arg) {
//尝试获取锁
if (!tryAcquire(arg) &&
//获取写锁失败加入到阻塞队列
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//获取当前线程
Thread current = Thread.currentThread();
int c = getState();
//写锁次数,就是为了判断是否有线程获取到写锁
int w = exclusiveCount(c);
if (c != 0) {
//有线程获取到读锁,但是没有线程获取到写锁
// c != 0 && w == 0: 写锁可用,但是有线程持有读锁(也可能是自己持有)
// c != 0 && w !=0 && current != getExclusiveOwnerThread(): 其他线程持有写锁
//直接返回false 排队去吧
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//写锁重入次数是否大于最大值
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//设置写锁状态,此时代表锁重入
setState(c + acquires);
return true;
}
//没有读锁 没有写锁 cas 抢锁
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
unlock
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//释放锁成功唤醒队列线程
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease
protected final boolean tryRelease(int releases) {
//是否是当前线程,不是当前线程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//状态减
int nextc = getState() - releases;
//写锁是否等于0
boolean free = exclusiveCount(nextc) == 0;
//如果等于0写锁释放,获取写锁线程置空
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}