理解ReentrantReadWriteLock

本文主要内容是对并发包中的读写锁的认识,主要解释读写锁的请求过程,锁降级的实现以及锁升级的不可能性。

首先来了解一些常量和简单方法,贴下代码

 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;

 /** Returns the number of shared holds represented in count  */
 static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
 /** Returns the number of exclusive holds represented in count  */
 static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

    上述代码中有四个常量,先来简单认识下,SHARED_UNIT=65536,MAX_COUNT=EXCLUSIVE_MASK=65535

如果用二进制来表示SHARED_UNIT的第17位是1,后面16个0;65535的前面16位都是1。sharedCount(int c)方法

计算的是二进制中17位起的值,exclusiveCount(int c)计算的是c前面16位的值。在读写锁的源码中,读取锁和写入锁的共有一个state变量计算,写入线程获取锁将state变量加1(在state的低16位内操作),读取线程获取锁将state变量加65536(在state的17位起操作的),看看代码中的英文解释可以理解大概意思,读取线程获取锁的方式是共享模式的,而写入线程的是独占模式的,下面通过代码来认识它的特点。

先来认识下,读取线程的请求锁代码

 protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail
     * 2. If count saturated, throw error
     * 3. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 4. If step 3 fails either because thread
     *    apparently not eligible or CAS fails,
     *    chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    if (sharedCount(c) == MAX_COUNT)
        throw new Error("Maximum lock count exceeded");
    if (!readerShouldBlock(current) &&
        compareAndSetState(c, c + SHARED_UNIT)) {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != current.getId())
            cachedHoldCounter = rh = readHolds.get();
        rh.count++;
        return 1;
    }
    return fullTryAcquireShared(current);
}

  

代码的逻辑已经有详细的英文解释了,在这里说点别的:

exclusiveCount(c)方法它返回的是低16位的值,更彻底的说是只关心低16位的值,也就是说是否有写入线程持有锁

,如果持有的话就失败;sharedCount(c)方法是将c右移16位,返回的17位起的值,如果返回值为65535,则请求失败。再来看看readerShouldBlock(current)方法,这个方法在AQS中的关键代码如下

 /**
	 * Return {@code true} if the apparent first queued thread, if one
	 * exists, is not waiting in exclusive mode. Used only as a heuristic
	 * in ReentrantReadWriteLock.
	 */
	final boolean apparentlyFirstQueuedIsExclusive() {
	    Node h, s;
	    return ((h = head) != null && (s = h.next) != null &&
	            s.nextWaiter != Node.SHARED);
	}

同步队列中的第一个等待者是独占模式的(在这里只有独占模式和共享模式)情况就返回true,否则返回false,换句话说就是第一个等待者是写入线程,它就返回true,即读线程应该阻塞,否则读线程不应该阻塞,这里是降级锁实现的关键。

在这里先来对Node节点中的nextWaiter域的应用做个总结(可以结合另一边文章关于ReentrantLock中condition的理解)nextWaiter有两个作用:一、在共享模式中做标记作用,独占模式是static final Node EXCLUSIVE = null;共享模式是static final Node SHARED = new Node();在来看看Node的一个构造方法

  Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

所以nextWaiter在这里起标记作用,其中的共享模式是一个空的节点;

二、在条件队列中做指针,但在条件队列中节点的构造方法采用的是

 Node(Thread thread, int waitStatus) { // Used by Condition
  this.waitStatus = waitStatus;
 this.thread = thread;
}

其中waitStatus=-2,nextWaiter用来指向实际的“条件节点”(由上述构造方法产生的节点),在实际使用中不会产生歧义的。在回到readerShouldBlock(current)方法,意义就是同步队列中第一个等待者是写入线程就返回true。

现在回到读取线程的请求方法tryAcquireShared(int unused)中,来看看readHoldsd的作用,在源码中的解释如下

/**
* The number of read locks held by current thread.
 * Initialized only in constructor and readObject.
*/
 transient ThreadLocalHoldCounter readHolds;

翻译为当前线程的读取锁计数,这是因为读取锁是共享模式的,多个读取线程都可以进行compareAndSetState(c, c +SHARED_UNIT)操作,同时单个线程又是可重入的,所以要记录每个线程的读取锁记录;写入锁由于是独占模式的,所以没有这个问题。

     下面来说下锁降级的实现,读写锁的锁降级指的是:在持有写入锁时,再去持有读取锁,然后释放写入锁,此时还持有读取锁。首先写入线程正常获取独占锁,在读取线程请求锁的时候,方法readerShouldBlock(current)返回false(因没有等待者),最后tryAcquireShared(int unused)方法返回1,获得许可,此时的state=65537,线程同时拥有读写锁,写入锁释放后,线程仍然持有读取锁。以下是锁降级的示例代码

// 锁降级:首先获取写入锁,然后获得读取锁,释放写入锁,释放读取锁
public String putAndGet(String key,String value){
	String s="";
	w.lock();
	try{
		s=m.put(key, value);
		r.lock();
		m.get(key);
	} finally{
		w.unlock();
		r.unlock();
	}
	return s;
}

注意:如果是先获得读取锁,在获得写入锁,线程将被park,并且其他线程将不能获得任何一个锁,这是为什么?

先来看看写入线程请求锁的代码

 protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. if read count nonzero or write count nonzero
     *     and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;   //  标记1
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if ((w == 0 && writerShouldBlock(current)) ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

代码会在标记1处由于c=65536,w=0,当前线程不是独占线程而返回false,请求写入锁失败。再来看看读取线程是怎么失败的,程序会走到fullTryAcquireShared方法,代码如下

/**
 * Full version of acquire for reads, that handles CAS misses
 * and reentrant reads not dealt with in tryAcquireShared.
 */
final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = cachedHoldCounter;
    if (rh == null || rh.tid != current.getId())
        rh = readHolds.get();
    for (;;) {
        int c = getState();
        int w = exclusiveCount(c);
        if ((w != 0 && getExclusiveOwnerThread() != current) ||
            ((rh.count | w) == 0 && readerShouldBlock(current)))
            return -1; //标记2
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            cachedHoldCounter = rh; // cache for release
            rh.count++;
            return 1;
        }
    }
}

它在标记2处返回-1,表示读线程获取锁失败,将进入同步队列。所以从读取锁升级写入锁是不可能的。

最后就是锁的释放,在理解了本文,相信看读写锁的释放操作还是比较简单的。

猜你喜欢

转载自rxin2009.iteye.com/blog/1718447