网站建设费属于广告费,做网站就找喇叭人,网站cname解析,万州网站制作四、读锁分析
4.1 读锁加锁流程概述
1、分析读锁加速的基本流程 2、分析读锁的可重入锁实现以及优化 3、解决ThreadLocal内存泄漏问题 4、读锁获取锁自后#xff0c;如果唤醒AQS中排队的读线程
4.1.1 基础读锁流程
针对上述简单逻辑的源码分析
// 读锁加锁的方法入口
pub…四、读锁分析
4.1 读锁加锁流程概述
1、分析读锁加速的基本流程 2、分析读锁的可重入锁实现以及优化 3、解决ThreadLocal内存泄漏问题 4、读锁获取锁自后如果唤醒AQS中排队的读线程
4.1.1 基础读锁流程
针对上述简单逻辑的源码分析
// 读锁加锁的方法入口
public final void acquireShared(int arg) {
// 竞争锁资源滴干活
if (tryAcquireShared(arg) 0)
// 没拿到锁资源去排队
doAcquireShared(arg);
}
// 读锁竞争锁资源的操作
protected final int tryAcquireShared(int unused) {
// 拿到当前线程
Thread current Thread.currentThread();
// 拿到state
int c getState();
// 拿到state的低16位判断 ! 0有写锁占用着锁资源
// 并且当前占用锁资源的线程不是当前线程
if (exclusiveCount(c) ! 0 getExclusiveOwnerThread() ! current)
// 写锁被其他线程占用无法获取读锁直接返回 -1去排队
return -1;// 没有线程持有写锁、当前线程持有写锁
// 获取读锁的信息state的高16位。
int r sharedCount(c);
// 公平锁就查看队列是由有排队的有排队的直接告辞进不去if后面也不用判断没人排队继续走
// 非公平锁没有排队的直接抢。 有排队的但是读锁其实不需要排队如果出现这个情况大部分是写锁资源刚刚释放
// 后续Node还没有来记得拿到读锁资源当前竞争的读线程可以直接获取
if (!readerShouldBlock()
// 判断持有读锁的临界值是否达到
r MAX_COUNT
// CAS修改state对高16位进行 1
compareAndSetState(c, c SHARED_UNIT)) {
// 省略部分代码
return 1;
}
return fullTryAcquireShared(current);
}
// 非公平锁的判断
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h head) ! null // head为null可以直接抢占锁资源
(s h.next) ! null // head的next为null可以直接抢占锁资源
!s.isShared() // 如果排在head后面的Node是共享锁可以直接抢占锁资源。
s.thread ! null; // 后面排队的thread为null可以直接抢占锁资源
}
4.1.2 读锁重入流程 重入操作 前面阐述过读锁为了记录锁重入的次数需要让每个读线程用ThreadLocal存储重入次数 ReentrantReadWriteLock对读锁重入做了一些优化操作 记录重入次数的核心 ReentrantReadWriteLock在内部对ThreadLocal做了封装基于HoldCount的对象存储重入次数在内部有个count属性记 录而且每个线程都是自己的ThreadLocalHoldCounter所以可以直接对内部的count进行操作。 第一个获取读锁资源的重入次数记录方式 第一个拿到读锁资源的线程不需要通过ThreadLocal存储内部提供了两个属性来记录第一个拿到读锁资源线程的信息 内部提供了firstReader记录第一个拿到读锁资源的线程firstReaderHoldCount记录firstReader的锁重入次数 最后一个获取读锁资源的重入次数记录方式 最后一个拿到读锁资源的线程也会缓存他的重入次数这样起来更方便 基于cachedHoldCounter缓存最后一个拿到锁资源现成的重入次数 最后一个获取读锁资源的重入次数记录方式 重入次数的流程执行方式 1、判断当前线程是否是第一个拿到读锁资源的如果是直接将firstReader以及firstReaderHoldCount设置为当前线程的 信息 2、判断当前线程是否是firstReader如果是直接对firstReaderHoldCount即可。 3、跟firstReader没关系了先获取cachedHoldCounter判断是否是当前线程。 3.1、如果不是获取当前线程的重入次数将cachedHoldCounter设置为当前线程。 3.2、如果是判断当前重入次数是否为0重新设置当前线程的锁从入信息到readHoldsThreadLocal中算是初始化 操作重入次数是0 3.3、前面两者最后都做count 上述逻辑源码分析
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)) {
//
// 判断r 0当前是第一个拿到读锁资源的线程
if (r 0) {
// 将firstReader设置为当前线程
firstReader current;// 将count设置为1
firstReaderHoldCount 1;
}
// 判断当前线程是否是第一个获取读锁资源的线程
else if (firstReader current) {
// 直接。
firstReaderHoldCount;
}
// 到这就说明不是第一个获取读锁资源的线程
else {
// 那获取最后一个拿到读锁资源的线程
HoldCounter rh cachedHoldCounter;
// 判断当前线程是否是最后一个拿到读锁资源的线程
if (rh null || rh.tid ! getThreadId(current))
// 如果不是设置当前线程为cachedHoldCounter
cachedHoldCounter rh readHolds.get();
// 当前线程是之前的cacheHoldCounter
else if (rh.count 0)
// 将当前的重入信息设置到ThreadLocal中
readHolds.set(rh);
// 重入的
rh.count;
}
//
return 1;
}
return fullTryAcquireShared(current);
}
4.1.3 读锁加锁的后续逻辑fullTryAcquireShared
// tryAcquireShard方法中如果没有拿到锁资源走这个方法尝试再次获取逻辑跟上面基本一致。
final int fullTryAcquireShared(Thread current) {
// 声明当前线程的锁重入次数
HoldCounter rh null;
// 死循环
for (;;) {
// 再次拿到state
int c getState();
// 当前如果有写锁在占用锁资源并且不是当前线程返回-1走排队策略
if (exclusiveCount(c) ! 0) {
if (getExclusiveOwnerThread() ! current)
return -1;
}
// 查看当前是否可以尝试竞争锁资源公平锁和非公平锁的逻辑
else if (readerShouldBlock()) {
// 无论公平还是非公平只要进来就代表要放到AQS队列中了先做一波准备
// 在处理ThreadLocal的内存泄漏问题
if (firstReader current) {
// 如果当前当前线程是之前的firstReader什么都不用做
} else {
// 第一次进来是null。if (rh null) {
// 拿到最后一个获取读锁的线程
rh cachedHoldCounter;
// 当前线程并不是cachedHoldCounter没到拿到
if (rh null || rh.tid ! getThreadId(current)) {
// 从自己的ThreadLocal中拿到重入计数器
rh readHolds.get();
// 如果计数器为0说明之前没拿到过读锁资源
if (rh.count 0)
// remove避免内存泄漏
readHolds.remove();
}
}
// 前面处理完之后直接返回-1
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)) {
if (sharedCount(c) 0) {
firstReader current;
firstReaderHoldCount 1;
} else if (firstReader current) {firstReaderHoldCount;
} else {
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;
}
}
}
4.1.4 读线程在AQS队列获取锁资源的后续操作 1、正常如果都是读线程来获取读锁资源不需要使用到AQS队列的直接CAS操作即可 2、如果写线程持有着写锁这是读线程就需要进入到AQS队列排队可能会有多个读线程在AQS中。 当写锁释放资源后会唤醒head后面的读线程当head后面的读线程拿到锁资源后还需要查看next节点是否也是读线程 在阻塞如果是直接唤醒 // 读锁需要排队的操作
private void doAcquireShared(int arg) {
// 声明Node类型是共享锁并且扔到AQS中排队
final Node node addWaiter(Node.SHARED);
boolean failed true;
try {
boolean interrupted false;
for (;;) {
// 拿到上一个节点
final Node p node.predecessor();
// 如果prev节点是head直接可以执行tryAcquireShared
if (p head) {
int r tryAcquireShared(arg);
if (r 0) {
// 拿到读锁资源后需要做的后续处理
setHeadAndPropagate(node, r);
p.next null; // help GC
if (interrupted)
selfInterrupt();
failed false;
return;
}
}
// 找到prev有效节点将状态设置为-1挂起当前线程
if (shouldParkAfterFailedAcquire(p, node)
parkAndCheckInterrupt())
interrupted true;}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHeadAndPropagate(Node node, int propagate) {
// 拿到head节点
Node h head;
// 将当前节点设置为head节点
setHead(node);
// 第一个判断更多的是在信号量有处理JDK1.5 BUG的操作。
if (propagate 0 || h null || h.waitStatus 0 || (h head) null || h.waitStatus 0) {
// 拿到当前Node的next节点
Node s node.next;
// 如果next节点是共享锁直接唤醒next节点
if (s null || s.isShared())
doReleaseShared();
} 4.2 读锁的释放锁流程
// 读锁释放锁流程
public final boolean releaseShared(int arg) {
// tryReleaseShared处理state的值以及可重入的内容
if (tryReleaseShared(arg)) {
// AQS队列的事
doReleaseShared();
return true;
}
return false;
}
// 1、 处理重入问题 2、 处理state
protected final boolean tryReleaseShared(int unused) {
// 拿到当前线程
Thread current Thread.currentThread();
// 如果是firstReader直接干活不需要ThreadLocal
if (firstReader current) {
// assert firstReaderHoldCount 0;
if (firstReaderHoldCount 1)
firstReader null;
else
firstReaderHoldCount--;
}
// 不是firstReader从cachedHoldCounter以及ThreadLocal处理else {
// 如果是cachedHoldCounter正常--
HoldCounter rh cachedHoldCounter;
// 如果不是cachedHoldCounter从自己的ThreadLocal中拿
if (rh null || rh.tid ! getThreadId(current))
rh readHolds.get();
int count rh.count;
// 如果为1或者更小当前线程就释放干净了直接remove避免value内存泄漏
if (count 1) {
readHolds.remove();
// 如果已经是0没必要再unlock扔个异常
if (count 0)
throw unmatchedUnlockException();
}
// -- 走你。
--rh.count;
}
for (;;) {
// 拿到state高16位-1成功后返回state是否为0
int c getState();
int nextc c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
return nextc 0;
}
}
// 唤醒AQS中排队的线程
private void doReleaseShared() {
// 死循环
for (;;) {
// 拿到头
Node h head;
// 说明有排队的
if (h ! null h ! tail) {
// 拿到head的状态
int ws h.waitStatus;
// 判断是否为 -1
if (ws Node.SIGNAL) {
// 到这说明后面有挂起的线程先基于CAS将head的状态从-1改为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
// 唤醒后续节点
unparkSuccessor(h);
}
// 这里不是给读写锁准备的在信号量里说。。。
else if (ws 0 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
// 这里是出口
if (h head)
break;
}
}