出现线程安全问题的核心要素是什么?无外乎多个线程同时操作共享资源,那么为了避免出现线程问题只需要回避该要素,我们通常最先想到的方案是什么?加把锁!这样是能解决线程的安全问题,但是又会使程序的性能下降,遇到这样的问题该怎么办呢?
仔细分析多个线程同时操作共享资源这句话,会发现其中有同时操作共享资源这个操作,那么问题就明显了,我多个线程同时改变这个共享资源会出现线程安全问题,但是多个线程同时读取共享资源也会出现安全问题吗?
这样细化下来我们发现,不论对共享资源是修改还是读取都加锁,会损失性能,那么我们可不可以对锁进行细分,使读-读可并发,由此就引出了该文章,读写锁ReentrantReadWriteLock
图解流程
写锁上锁流程
static final class NonfairSync extends Sync {
// ...省略无关代码
//外部类WriteLock方法,方便阅读,放在此处
public void lock() {
sync.acquire(1);
}
// AQS继成来的方法,方便阅读,放此处
public final void acquire(int arg) {
if (
//尝试获閃锁失败
!tryAcquire(arg) &&
//将当前线程关联到一个Node对象上,模式为独占模式
//进入AQS队列阻塞
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
) (
selfInterrupt();
}
}
// Sync继承过来的方法,方便阅读,放此处
protected final boolean tryAcquire(int acquires) {
//获得低16位,代表写锁的state计数
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
if (
//c != 0 and w == 0表示有读锁,或者
w == 0 ||
// 如果 exclusiveOwnerThread 不是自己
current != getExclusiveOwnerThread()
) {
//获得锁趣
return false;
}
//写锁计数超过低16位,报异常
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//写锁重入,获得锁藏
setState(c + acquires);
return true;
}
if (
//判断写锁是否该阻塞,或者
writerShouldBlock() ||
//尝试更改计録败
!compareAndSetState(c, c + acquires)
) {
//获得锁趣
return false;
}
//获得锁翊
setExclusiveOwnerThread(current);
return true;
}
//非公平锁writerShouldBlock总是返回false,无需阻塞
final boolean writerShouIdBlock() {
return false;
}
}
写锁释放流程
static final class NonfairSync extends Sync {
// ...省略无关代码
// WriteLock方法,方便阅读,放在此处
public void unlock() {
sync.release(l);
}
// AQS继迎来的方法,方便阅读,放醐处
public final boolean release(int arg) {
//尝试释放写锁成功
if (tryRelease(arg)) {
// unpark AQS中等待的线程
Node h = head;
if (h != null && h.waitStatus != 0) unparkSuccessor(h);
return true;
}
return false;
}
// Sync继承过来的方法,方便阅读,放此处
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IHegalMonitorStateException();
int nextc = getState() - releases;
//因为可重入的原因,写锁计数为0 才算释放成功
boolean free = exclusiveCount(nextc) == 0;
if (free) {
setExclusiveOwnerThread(null);
}
setState(nextc);
return free;
}
}
读锁上锁流程
static final class NonfairSync extends Sync {
// ReadLock方法,方便阅读,放在此处
public void lock() {
sync.acquireShared(1);
}
// AQS继迎来的方法,方便阅读,放醐处
public final void acquireShared(int arg) {
// tryAcquireShared返回负数,表示获取读锁失败
if (tryAcquireShared(arg) < 0) {
doAcquireShared(arg);
}
}
// Sync继承演的方法,方便阅读,放此处
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 &&
//尝谏曾加计数成功
compareAndSetState(c, c + SHARED_UNIT)
) {
//...省略不重要的代码
return 1;
}
return fullTryAcquireShared(current);
}
//非公平锁readerShouldBlock看AQS队列中第—个节点是否是写锁
// true则该阻塞,false则不阻塞
final boolean readerShouIdBlock() {
return apparentlyFirstQueuedIsExclusive();
}
// AQS继承来的方法,方便阅读,放此处
//与tryAcquireShared功能类似,但会不断尝试for (;;)获取读锁,执行过程中无阻塞
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (; ; ) {
int c = getState();
if (exclusiveCount(c) != 0) {
if (getExclusiveOwnerThread() != current)
return -1;
} else if (readerShouldBlock()) {
// ...省略不重要的代码
}
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
if (compareAndSetState(c, c + SHARED_UNIT)) {
// ...省略不重要的代码
return 1;
}
}
}
// AQS继承来的方法,方便阅读,放此处
private void doAcquireShared(int arg) {
//将当前线程关联到一个Node对象上,模式为共享模式
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (; ; ) {
final Node p = node.predecessor();
if (p == head) {
//再一次尝试获取读锁
int r = tryAcquireShared(arg);
//成功
if (r >= 0) {
//㈠
// r表示可用资源数,在这里总是1允许传播
// (唤醒AQS中下一个Share节点)
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (
//是否在获取读锁失败时阻塞(前—个阶段waitStatus == Node.SIGNAL )
shouldParkAfterFailedAcquire(p, node) &&
// park当前线程
parkAndChecklnterrupt()
) {
interrupted = true;
}
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//㈠AQS继承过来的方法,方便阅读,放在此处
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; // Record old head for check below
//设置自己为head
setHead(node);
// propagate表示有共享资源(例如共享读锁或信号量)
// 原 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
// 在 head waitStatus == Node.SIGNAL 或 Node.PROPAGATE
if (propagate > 0 || h == null || h.waitStatus < 0 |
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//如果是最后一节点或者是等待共享读锁的节点
if (s == null || s.isShared()) {
//进入㈡
doReleaseShared();
}
}
}
//㈡AQS继迎来的方法,方便阅读,放在此处
private void doReleaseShared() {
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功,下一个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE,为了解决 bug,见后面分析
for (; ; ) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 下一个节点 unpark 如果成功获取读锁
// 并且下下个节点还是shared,继续doReleaseShared
unparkSuccessor(h);
} else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
读锁释放流程
static final class NonfairSync extends Sync {
// ReadLock方法,方便阅读,放在此处
public void unlock() {
sync.releaseShared(1);
}
// AQS继迎来的方法,方便阅读,放醐处
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
// Sync继承演的方法,方便阅读,放此处
protected final boolean tryReleaseShared(int unused) {
// ...省略不重要的代码
for (; ; ) {
int c = getState();
int nextc = c - SHAREDJJNIT;
if (compareAndSetState(c, nextc)) {
//读锁的诚不会影响其它获取读锁线程,但会影响其它获取写锁线程
// 诚为0才是真正释放
return nextc == 0;
}
}
}
// AQS继承来的方法,方便阅读,放此处
private void doReleaseShared() {
// 如果 head.waitStatus == Node.SIGNAL ==> 0 成功,下―个节点 unpark
// 如果 head.waitStatus == 0 ==> Node.PROPAGATE
for (; ; ) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//如果有其它线程也在释放读锁,那么需要将waitStatus先改为0
//防止unparkSuccessor被多涓丸行
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//如果已经是。了,改为-3 ,用来解决传播性,见后文信号量bug分析
else if (ws == 0 &&
!compareAndSetWaitStatus(h, Node.PROPAGATE)) continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
}
注意事项
- 读锁不支持条件变量
- 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致写锁永久等待
- 重入时降级支持:即持有写锁的情况下去获取读锁
进一步优化读
StampedLock
该类自JDK8加入,是为了进一步优化读性能,大的特点是在使用读锁、写锁时配合使用【戳】
加解读锁、
long stamp = lock.readLock();
lock.unlockRead(stamp);
加解写锁
long stamp = lock.writeLock();
lock.unlockWrite(stamp);
乐观读
StampedLock支持tryOptimisticRead()方法(乐观读),读取完毕后需做一次戳校验如果校验通过,表示改期间确实没有写操作,数据可以安全使用,如果校验未通过,则需要重新获取读锁,保证数据安全
long stamp = lock.tryOptimisticRead();
//验戳
if(!lock.validate(stamp)){
//锁升级
}
以上就是该文章的全部内容
下一篇:Semaphore、CountdownLatch、CyclicBarrier
该文章是作者学习juc所整理的笔记主要的知识来源是黑马的juc系列,如有问题欢迎指正