JDK7中ReentrantReadWriteLock源码解析(2)

来源:互联网 发布:淘宝联盟 微信遭投诉 编辑:程序博客网 时间:2024/06/07 22:02

在阅读此篇文章前请确保对重入锁获取锁的源码有一定的了解,如果对重入锁获取锁源码不太清楚的可以参考: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;} // 返回独占锁持有计数

    // ...省略其它代码

}

 

ReentrantReadWriteLock写锁的获取源码解析:

 

如果其它的线程没有获得读锁与写锁,则立即返回获得写锁,然后设置获得写锁持有数量为1。如果当前已经获得写锁则持有数量增加1后立即返回。如果锁被另一个线程持有,那么当前线程就会因为线程调度的目的而被禁用,并且处于休眠状态,直到获得了写锁,此时写入锁的计数将被设置为1。

public void lock(){

 sync.acquire(1);

}

 

此处实现类似ReentrantLock获取锁的实现,只是tryAcquire(arg)实现不一致

public final void acquire(int arg){

 if(!tryAcquire(arg)&&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))

    selfInterrupt();

}

acquireQueued:获取在队列中已经存在的线程的独占的不可中断模式,被Condition的wait和acquire方法使用

addWaiter:给当前指定模式的线程创建节点,并且把当前节点添加到队列【队列不存在则创建】

selfInterrupt:中断当前线程以实现自我中断

 

对于尝试获取锁:如果读或者写的计数非0则表明锁被不同的线程持有,返回fail;如果计数将要饱和,返回fail,计数非0的时候才有可能发生;如果该线程是可重入的获取或遵循某种队列策略,则该线程可以被锁定,如果是则更新同步状态并且设置当前线程独占所有者。

protected final boolean tryAcquire(int acquires){

 Thread current=Thread.currentThread();

 int c=getState(); // 获取同步状态,AQS中的state

 int w=exclusiveCount(c); // 返回独占锁的持有计数

 if(c!=0){ // 不确定是什么线程获得什么锁,但是可以确定有线程获得锁 

   if(w==0||current!=getExclusiveOwnerThread())

     // 其它线程获得读锁而导致共享锁计数不为0,其它线程获得写锁也会返回false

     return false;

   if(w+exclusiveCount(acquires)>MAX_COUNT)

     throw new Error("Maximum lock count exceeded"); // 锁的技术不能够饱和

   setState(c+acquires); // 写锁的重入,更新同步状态state的值

   return true; // 返回尝试获取锁成功

 }

 // writerShouldBlock在公平锁和非公平锁中的实现是不相同的

 // 可以参考:http://ericchunli.iteye.com/blog/2395841中的实现解析

 // 如果需要阻塞或者state的值被改变则返回false

 if(writerShouldBlock()||!compareAndSetState(c,c+acquires))

   // 此处应该只能说明存在线程获得锁,但是什么线程获得什么锁应该是不确定的

   return false;

  // 设置当前拥有独占访问权的线程,null表示没有线程拥有访问权,此方法不会强制执行任何同步或volatile字段访问

  setExclusiveOwnerThread(current);

  return true;

}

 

ReentrantReadWriteLock写锁的释放源码解析:

 

如果当前线程是此锁的持有者则持有计数将递减,如果持有数现在为零则锁被释放。如果当前线程不是该锁的持有者则抛出IllegalMonitorStateException。

public void unlock() {

  sync.release(1);

}

 

独占模式的释放,如果线程(可多个)实现了unblocking则返回true,可用于实现Lock#unlock方法

public final boolean release(int arg){

 if(tryRelease(arg)){

   Node h=head; // CLH队列节点 

   if(h!=null&&h.waitStatus!=0) // waitStatus用来表示当前节点状态 

     unparkSuccessor(h); // 如果存在后继节点则唤醒

   return true;

 }

 return false;

}

 

tryRelease和tryAcquire能够被Conditions调用,因此它们的参数可能包含读和写,这些都是在condition等待和tryAcquire中的重新建立的过程中被释放

protected final boolean tryRelease(int releases){

 if(!isHeldExclusively())

   // getExclusiveOwnerThread()== Thread.currentThread(); 

   // 虽然必须在所有者面前读取状态,但不需要检查当前线程是否为所有者

   throw new IllegalMonitorStateException();

  // 获取AQS的state的新值

  int nextc=getState()-releases;  

  boolean free=exclusiveCount(nextc)==0; // 不为0表示存在重入

  if(free)

   setExclusiveOwnerThread(null); // 设置为null表示释放锁

  setState(nextc); // 更新同步状态state的值

  return free;

}

 

实现总结:ReentrantReadWriteLock.WriteLock是通过Sync来实现锁的获取与释放的,锁的获取对于公平锁和非公平锁是类似的,差异表现在锁的阻塞策略上,即writerShouldBlock的实现方式不一致,对于非公平锁而言,写锁总是优先获取,不考虑AQS队列中先来的线程;对于公平锁而言,需要判断是否存在等待更久的线程,同ReentrantLock实现一致。

 

ReentrantReadWriteLock.WriteLock通过获取同步状态(AQS中的state值),独占锁的持有计数,exclusiveOwnerThread来判断锁的获取【state!=0时的处理方式】,成功则返回true且更新同步状态state的值;也可通过writerShouldBlock()和CAS(state)的值来判断锁的获取,成功则返回true且更新同步状态state的值和设置当前线程为独占线程拥有者。

 

尝试获取失败则会把当前节点压入CLH队列,若CLH队列不为空则将新创建的Node添加到队列尾部,若CLH队列为空则新创建队列后把节点添加到队列中,Node节点压入队列后会重新尝试获得锁,可能会存在休眠或者取消时阻塞并返回中断状态【会导致自我中断的实现】,如果失败获取锁则会进行取消正在进行的获取的尝试,同ReentrantLock的实现一致

 

ReentrantReadWriteLock.WriteLock通过获取同步状态state的值独占锁的持有计数,exclusiveOwnerThread来实现锁的释放;无论锁在此处是否被释放,都需要重置同步状态state的值,如果独占锁的持有计数为0则会设置exclusiveOwnerThread为null【state为0】来实现锁的释放。

 

Note:相比ReentrantReadWriteLock.ReadLock而言,写锁的释放和获取还算简单,关于读锁的释放与获取,可参考http://ericchunli.iteye.com/blog/2396018。

原创粉丝点击