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重置状态是否失败,如果需要重新检查的话。

原创粉丝点击