JDK7中ReentrantReadWriteLock源码解析(3)
来源:互联网 发布:淘宝联盟 微信遭投诉 编辑:程序博客网 时间:2024/06/05 22:38
在阅读此篇文章前请确保对重入锁获取锁的源码有一定的了解,如果对重入锁获取锁源码不太清楚的可以参考:http://ericchunli.iteye.com/blog/2393222。ReentrantReadWriteLock中存在抽象内部类Sync,Sync用来实现所有的同步机制。这里提供读和写计数提取常量和方法,Lock的状态在逻辑上被切分为无符号的短整型两部分:低位代表独占写锁计数,高位代表共享读锁计数。
abstract static class Sync extends AbstractQueuedSynchronizer{
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;
static int sharedCount(int c){ return c>>>SHARED_SHIFT;} // 返回共享锁持有计数
static int exclusiveCount(int c){ return c&EXCLUSIVE_MASK;} // 返回独占锁持有计数
private transient Thread firstReader = null; // 应该是表示CLH队列中的首个线程
private transient int firstReaderHoldCount;
// 每个线程读取的计数器计数,ThreadLocal维护,cachedHoldCounter中缓存
static final class HoldCounter{
int count = 0;
final long tid = Thread.currentThread().getId(); // 为避免垃圾保留,使用Id而不是引用
}
// 当前线程拥有的读锁数量,由构造函数和readObject初始化,读锁数量为0时被清除
private transient ThreadLocalHoldCounter readHolds;
Sync(){
readHolds=new ThreadLocalHoldCounter();
setState(getState()); // 确保readHolds的可见性
}
// 此处应该是序列化时从流中重构这个锁实例
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
readHolds = new ThreadLocalHoldCounter();
setState(0); // 重置到未阻塞状态
}
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter>{
public HoldCounter initialValue(){ return new HoldCounter(); }
}
保存最后成功获取readLock的计数的线程,在下个线程中释放的线程是最后的线程可节省ThreadLocal查找。这是non-volatile的且对于线程缓存非常好。缓存读保存数的线程,但不保留对线程的引用,从而避免垃圾保留。通过良性数据竞争访问,依赖于内存模型的最终字段和out-of-thin-air保证。
private transient HoldCounter cachedHoldCounter; // 晦涩难懂难理解,不理解
// ...省略其它代码
}
ReentrantReadWriteLock读锁的获取源码解析:
如果写锁不被其它线程持有并立即返回则获得读锁;如果写锁被其它线程持有,那么当前线程将被禁用于线程调度的目的,并休眠直到读取锁定为止。
public void lock(){
sync.acquireShared(1);
}
共享模式的获取且忽略中断,首先至少调用一次{tryAcquireShared}且返回成功;否则线程将排队且可能反复阻塞和未阻塞,调用{tryAcquireShared}直到成功。
public final void acquireShared(int arg){
if(tryAcquireShared(arg)< 0)
doAcquireShared(arg);
}
如果写锁被其它线程拥有则返回失败;否则该线程就能够对wrt状态进行lock,因此询问它是否应该因为队列策略而阻塞,如果没有则尝试通过CASing状态和更新计数来授予。注意步骤不检查重入获取,它被推迟到完整版本以避免在更典型的非重入情形中检查持有计数,这里的失败是因为线程显然不合格或CAS失败或计数饱和,用完整的重试循环进行链到版本。
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); // shared计数器数量,shared应该是指分享锁:读锁
// 公平锁readerShouldBlock:判断是否队列中存在更长的等待
// 非公平锁readerShouldBlock:CLH队列中首尾节点不为空,独占式的,入当前节点队列的thread不为null
if(!readerShouldBlock()&&r<MAX_COUNT&&compareAndSetState(c,c+SHARED_UNIT)){
if(r==0){ // r==0表示没有线程获得读锁
firstReader=current;
firstReaderHoldCount=1;
}else if(firstReader==current){ // 这里应该表示读锁的重入
firstReaderHoldCount++;
}else{ // 这里应该表示存在线程获取读锁
// 最后成功获取readLock的计数的线程,读锁数量的缓存
HoldCounter rh=cachedHoldCounter;
if(rh==null||rh.tid!=current.getId())
// 不存在读锁缓存线程或者非当前线程获得读锁时,当前线程的读锁设置
cachedHoldCounter=rh=readHolds.get();
else if(rh.count==0)
// 当前线程存在读锁缓存,但是计数为0,应该是初始化或者释放锁导致
readHolds.set(rh); // 当前线程增加读锁
rh.count++; // 读锁计数的增加
}
return 1; // 表示成功获得锁
}
// 处理CAS丢失和重入读未处理
return fullTryAcquireShared(current);
}
获取读锁的完整版本,处理CAS丢失和重入读未处理的tryAcquireShared,不要让tryAcquireShared与重试和延迟读取的交互进行交互
final int fullTryAcquireShared(Thread current){
// 可参考tryAcquireShared(int unused)的实现解析
HoldCounter rh = null;
for(;;){ // 自旋直到返回锁的获取或者JVM处理自旋结束
int c=getState();
if(exclusiveCount(c)!=0){ // 表示存在线程获得写锁
if(getExclusiveOwnerThread()!=current)
return -1;
// else表示当前线程获得写锁,阻塞将导致死锁
}else if(readerShouldBlock()){ // 这里应该会导致自旋
// 确保没有重新获得读锁
if(firstReader==current){
// assert firstReaderHoldCount>0;
}else{
if(rh==null){
rh=cachedHoldCounter;
if(rh==null||rh.tid!=current.getId()){
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");
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!=current.getId())
rh = readHolds.get();
else if(rh.count==0)
readHolds.set(rh);
rh.count++;
cachedHoldCounter=rh;
}
return 1;
}
}
}
尝试获取锁失败后,共享的不可中断模式下获取
private void doAcquireShared(int arg){
// 当前线程以给定模式创建节点,并将节点压入CLH队列
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){
setHeadAndPropagate(node,r); // 设置头节点和传播属性
p.next = null; // help GC
if(interrupted)
selfInterrupt(); // 自我中断,重置中断标志
failed = false;
return;
}
}
if(shouldParkAfterFailedAcquire(p, node)&&parkAndCheckInterrupt())
interrupted = true; // 中断会清除中断标志
}
}finally{
if(failed) // 获取失败最终会取消正在进行的获取的尝试
cancelAcquire(node);
}
}
设置队列头,并检查后继节点是否在共享模式下等待,如果propagate>0或PROPAGATE状态被设置,则进行传播。
private void setHeadAndPropagate(Node node, int propagate) {
Node h = head;
setHead(node);
//尝试唤醒下个排队节点:调用者表示传播或者通过先前的操作记录(如h.waitStatus)(注意:这里使用了waitStatus的签名检查,因为PROPAGATE状态可能转换为SIGNAL);下个节点在共享模式下等待或者我们不知道,因为它似乎是null。这两种检查中的保守主义可能会导致不必要的唤醒,但只有当有多个竞争锁获得/释放时,所以大多数情况下需要信号。
if (propagate > 0 || h == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared(); // 为共享模式释放操作,唤醒后继线程并确保传播
}
}
为共享模式释放操作,唤醒后继线程并确保传播。注意:对于独占模式如果需要唤醒,释放就相当于(amounts to)调用头节点的unparkSuccessor。确保释放的传播,即使有其它正在进行的获取/释放操作。如果它需要唤醒,这将以常见的方式来尝试去unpark头节点的后继者。但如果它不存在,则将状态设置为PROPAGATE,以确保在释放时继续传播。此外也必须循环,以防在执行此操作时添加新节点。另外不像其它使用unparkSuccessor的方法,这需要知道CAS重置状态是否失败,如果需要重新检查的话。
private void doReleaseShared() {
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
unparkSuccessor(h); // 唤醒后继节点
}else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}
检查和更新一个未获得锁的节点的状态,如果线程阻塞则返回true,这是所有获取循环中的主唤醒控制,要求pred == node.prev。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
// 节点已经设置了状态,请求释放来唤醒它,这样它就可以安全地阻塞了
return true;
if (ws > 0) {
// 前继节点已经取消,跳过前继节点以及指示重试
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// waitStatus必须为0或PROPAGATE,这表明需要唤醒但不要阻塞。调用者需要重试,以确保在阻塞前不能获得。
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
阻塞当前线程,返回中断状态(应该是被唤醒后的中断状态)
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
Note:当线程被解除阻塞(被中断或调用unpark方法,前继节点的线程唤醒)的时候会返回中断的状态。
取消正在进行的获取的尝试
private void cancelAcquire(Node node) {
if(node == null) return; // 节点不存在则直接忽略
node.thread = null;
Node pred = node.prev; // 跳过取消的前继节点
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext是unsplice的明显节点,不是这样的话CAS将会失败,这种情况会失去竞争vs其它取消或唤醒,所以没有必要采取进一步的行动
Node predNext = pred.next;
//可用无条件的写代替CAS,当前原子步骤之后,其它节点可以跳过,在此之前不受其它线程的干扰。
node.waitStatus = Node.CANCELLED;
// 尾部节点直接移除
if(node == tail && compareAndSetTail(node, pred)){
compareAndSetNext(pred, predNext, null);
}else{
// 如果后置节点需要被唤醒,尝试设置pred的下一个链接,这样就会得到一个,否则把它唤醒以传播。
int ws;
if(pred != head && ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0
&& compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && pred.thread != null){
Node next = node.next;
if(next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
}else{
unparkSuccessor(node); // 唤醒后继节点
}
node.next = node; // help GC
}
}
ReentrantReadWriteLock中读锁的释放:
试图释放这个锁,如果现在的读锁数量为0,那么锁就可以用于写锁尝试
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) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--; // 这里说明存在重入
} else { // 对于缓存的处理
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != current.getId())
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove(); // help GC
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count; // 多次的释放
}
for (;;) { // 自旋的处理
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// 释放读锁对读线程没有影响,但如果读和写锁都是自由的,它可能允许等待的写线程继续进行。
return nextc == 0;
}
}
实现总结:ReentrantReadWriteLock.ReadLock是通过Sync来实现锁的获取与释放的,锁的获取对于公平锁和非公平锁是类似的,差异表现在锁的阻塞策略上,即readerShouldBlock的实现方式不一致。相对于ReentrantReadWriteLock.WriteLock而言,ReentrantReadWriteLock.ReadLock多提供了以下参数来实现锁的获取与释放:
HoldCounter cachedHoldCounter:保存最后成功获取readLock的计数的线程,ThreadLocal维护;ThreadLocalHoldCounter readHolds:当前线程拥有的读锁数量,它继承了ThreadLocal,初始化值是HoldCounter实例;Thread firstReader和firstReaderHoldCount;
Note:HoldCounter只存在两个变量count=0和long tid=Thread.currentThread().getId();使用ThreadLocal的原因,估计是因为读锁可以被共享,用来为每个线程创建拥有读锁相关信息的本地变量。
写锁可以降级为读锁,但是读锁不能升级为读锁。如果存在非当前线程获得写锁则直接返回获取锁失败;如果当前线程需要阻塞(readerShouldBlock返回true)则进行自旋,直到锁的获取或者JVM结束自旋;自旋尝试获取失败后会以共享的不可中断模式下继续自旋获取锁,此时会为当前线程创建节点并压入CLH队列,这里自旋会不断获取前继节点,然后根据waitStatus的值进行对应的处理,锁的释放并唤醒后继节点,如果获取锁的过程中产生了中断则会进行自我中断的实现,最终会导致取消正在进行的获取的尝试。获取锁成功后则会对cachedHoldCounter,firstReaderHoldCount,firstReader,state进行对应的值更新。
ReentrantReadWriteLock.ReadLock锁的释放也会对cachedHoldCounter,firstReaderHoldCount,firstReader,state进行对应的值更新。自旋以获取同步状态state的值,通过对state的设置来尝试进行锁的释放。在尝试释放锁成功返回的时候,会再次进行自旋来实现锁的释放。为共享模式释放操作,唤醒后继线程并确保传播。注意:对于独占模式如果需要唤醒,释放就相当于调用头节点的unparkSuccessor。确保释放的传播,即使有其它正在进行的获取/释放操作。如果它需要唤醒,这将以常见的方式来尝试去unpark头节点的后继者。但如果它不存在,则将状态设置为PROPAGATE,以确保在释放时继续传播。此外也必须循环,以防在执行此操作时添加新节点。另外不像其它使用unparkSuccessor的方法,这需要知道CAS重置状态是否失败,如果需要重新检查的话。
- JDK7中ReentrantReadWriteLock源码解析(3)
- JDK7中ReentrantReadWriteLock源码解析(1)
- JDK7中ReentrantReadWriteLock源码解析(2)
- JDK7中ReentrantLock源码解析(3)
- JDK7中LockSupport源码解析
- JDK7中AtomicInteger源码解析
- JDK7中ArrayBlockingQueue源码解析
- JDK7中LinkedBlockingQueue源码解析
- JDK7中SynchronousQueue源码解析
- JDK7中StringBuffer/StringBuilder源码解析
- JDK7中ReentrantLock源码解析(1)
- JDK7中ReentrantLock源码解析(2)
- Java 1.7 ReentrantReadWriteLock源码解析
- JDK7中Condition源码概述
- JDK7中Lock源码概述
- JDK7中ReadWriteLock源码概述
- JDK7中Executor源码概述
- JDK7中ExecutorService源码概述
- JDK7中ReentrantReadWriteLock源码解析(2)
- 阿里云vpc+nat网关+路由表的详解
- 系统日志输出工具类
- systemctl命令
- 枚举---枚举的其他应用
- JDK7中ReentrantReadWriteLock源码解析(3)
- 简单的delegate代理使用
- MYSQL面试题整合
- leetcode 669. Trim a Binary Search Tree
- 第五章数组与指针十个问题及解答
- TensorFlow的softmax regression做mnist例子
- JDK7中LockSupport源码解析
- 警惕!最新21家失信国际物流企业名录
- 2017校招总结