`
rxin2009
  • 浏览: 16782 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

理解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,表示读线程获取锁失败,将进入同步队列。所以从读取锁升级写入锁是不可能的。

 

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

 

 

 

分享到:
评论

相关推荐

    6.5 深入理解 AQS之 ReentrantReadWritelock 实战副本.mp4

    6.5 深入理解 AQS之 ReentrantReadWritelock 实战副本.mp4

    6.5 深入理解 AQS之 ReentrantReadWritelock 实战副本副本.mp4

    6.5 深入理解 AQS之 ReentrantReadWritelock 实战副本副本.mp4

    8、读写锁ReentrantReadWriteLock&StampLock详解.pdf

    7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    【2018最新最详细】并发多线程教程

    11.深入理解读写锁ReentrantReadWriteLock 12.详解Condition的await和signal等待通知机制 13.LockSupport工具 14.并发容器之ConcurrentHashMap(JDK 1.8版本) 15.并发容器之ConcurrentLinkedQueue 16.并发容器之...

    7、深入理解AQS独占锁之ReentrantLock源码分析(1).pdf

    7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    关于synchronized、Lock的深入理解

    目录synchronized的缺陷Lock和ReentrantLock常用方法ReadWriteLock和ReentrantReadWriteLockLock和synchronized区别synchronized锁升级公平锁和非公平锁 synchronized的缺陷 众所周知,synchronized锁是JAVA的关键字...

    Java并发编程原理与实战

    理解自旋锁,死锁与重入锁.mp4 深入理解volatile原理与使用.mp4 JDK5提供的原子类的操作以及实现原理.mp4 Lock接口认识与使用.mp4 手动实现一个可重入锁.mp4 AbstractQueuedSynchronizer(AQS)详解.mp4 使用AQS重写...

    6、JUC并发工具类在大厂的应用场景详解(1).pdf

    7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    10、阻塞队列BlockingQueue实战及其原理分析.pdf

    7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    9、并发容器(Map、List、Set)实战及其原理.pdf

    7、深入理解 AQS 独占锁之 Reentrantlock 源码分析 (1).pdf 8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    龙果 java并发编程原理实战

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    Java 并发编程原理与实战视频

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    龙果java并发编程完整视频

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    java并发编程

    第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 ...

    汪文君高并发编程实战视频资源下载.txt

    ReentrantReadWriteLock详细讲解_.mp4  高并发编程第三阶段28讲 Condition初步使用,提出几个疑问_.mp4  高并发编程第三阶段29讲 关于Condition疑问的几个小实验,对比Wait&amp;Notify_.mp4  高并发编程第三阶段...

    javaSE代码实例

    15.5 理解内部类 339 15.6 内部接口 340 15.6.1 定义在类中的内部接口 340 15.6.2 定义在接口中的内部接口 341 15.7 小结 342 第16章 多线程——Java中的并发协作 343 16.1 线程的基本知识 343 ...

Global site tag (gtag.js) - Google Analytics