前言
本篇主要来学习读写锁相关的一些知识。我们在并发中经常用到的是读多写少的场景,如果这时候使用读写锁分离的方式,能够大大提高我们的性能
知识点
读写锁之间非常巧妙的使用了state
的值,其中高16位代表读锁,低16位代表写锁
1.读写锁的实现
读写锁。既可以加读锁,也可以加写锁。
读写锁之间彼此互斥:加了读锁不能加写锁,加了写锁不能加读锁 写锁与写锁之间互斥:加了写锁之后其他写锁也会被挂起,入队 读锁与读锁之间共享:加了读锁之后,其他读锁也能获取到锁
2.ReentrantReadWriteLock的实现(AQS)
2.1.写锁
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);//低16位的值。写锁
if (c != 0) {//加过锁
// (Note: if c != 0 and w == 0 then shared count != 0)
//写锁为0,加的是读锁;或者写锁不为0,且当前线程不是已获取独占锁的线程,锁获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
//
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
从此处可以看到,通过判断state
是否为0,若为0,则说明已经加过锁。
w即低15位:写锁的标志位。若w==0(加的是读锁)或者或者当前线程不是加锁线程,否则加入可重入锁 若没有加过锁,则直接进行加锁 writerShouldBlock(): 非公平锁直接返回false,即不需要阻塞 公平锁判定是否有前任 hasQueuedPredecessors
,然后CAS修改状态,若修改成功,设置当前线程为exclusiveOwnerThread
,即占有锁的线程

2.2.可重入
假设线程1已经加过写锁,此时线程1再次获取写锁,那么可重入是如何实现的呢?
if (c != 0) {//加过锁
// (Note: if c != 0 and w == 0 then shared count != 0)
//写锁为0,加的是读锁;或者写锁不为0,且当前线程不是已获取独占锁的线程,锁获取失败
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
此时state!=0,则c!=0,w!=0(写锁),说明低16位不为0,则只需setState(c + acquires);
代码,此时state=2,由此可以看出如下结论
通过r(读锁),w(写锁)判断高低16位有没有加过读锁或者写锁,并且通过判段高低16位的值,来判断读锁和写锁的重入次数。 此时state的值不一定是2,如果既有写锁又有读锁的情况下,state的值可能是非常大,state的低16位的值才代表写锁的重入次数。
2.3.写锁失败时入队
当线程写锁失败时候,需要入队,代码逻辑如下
public final void acquire(int arg) {
//尝试加锁
if (!tryAcquire(arg) &&
//加锁失败
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//此处将之前的节点状态修改为-1
if (shouldParkAfterFailedAcquire(p, node) &&
//挂起当前线程
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}

线程1加了写锁,Node的waitestatus=-1,当前低16位因为线程1加了两次锁,state=2,锁占用的线程是线程1。head=tail=线程1的node 线程2请求加写锁,此时加锁失败,会在tail后面拼接一个新的node,waiteStatus=0
2.4.读写锁互斥
写锁-->读锁的互斥
protected final boolean tryAcquire(int acquires) {
.......
if (c != 0) {
//
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
c即state不为0,w==0(写锁为0,即加的是读锁),返回false,加锁失败。入队
读锁-->写锁的互斥
protected final int tryAcquireShared(int unused) {
int c = getState();
//低16位加锁了,及已经加了写锁,且不是当前线程,失败入队
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
return 1;
2.5.锁的释放
写锁的释放
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//唤醒继任者
unparkSuccessor(h);
return true;
}
return false;
}
//解锁的逻辑
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//当前state的值减去1,解锁1次,减一(可重入)
int nextc = getState() - releases;
//低16位为0,则说明解锁成功
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
尝试释放锁成功的话,如果头结点不为空,并且头结点waiteStatus!=0(0代表未解锁成功),则唤醒继任者 释放锁:将state进行减一操作,若操作后state的低16位为0,则解锁成功。
读锁的释放
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;
}
处理高16位的值之后判断是否为0,若为0则解锁成功
公众号