`
rxin2009
  • 浏览: 17367 次
  • 性别: 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,表示读线程获取锁失败,将进入同步队列。所以从读取锁升级写入锁是不可能的。

 

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

 

 

 

分享到:
评论

相关推荐

    Java 多线程与并发(12-26)-JUC锁- ReentrantReadWriteLock详解.pdf

    通过理解ReentrantReadWriteLock的工作原理和设计,开发者可以更好地在多线程环境中控制并发访问,提高程序的效率和安全性。在面试或实际开发中,掌握这些知识点对于解决复杂并发问题是至关重要的。

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

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

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

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

    深入浅出ReentrantReadWriteLock源码解析.docx

    总之,ReentrantReadWriteLock是Java并发编程中非常重要的一个工具,理解其内部机制有助于编写更高效、更可靠的多线程程序。通过深入研究源码,我们可以更好地掌握并发控制的策略,以及如何在实际项目中应用这些策略...

    图灵Java高级互联网架构师第6期并发编程专题笔记.zip

    内容包括 01-并发编程之深入理解JMM&并发三大特性(一)-fox 02-并发编程之深入理解JMM&并发三...11-深入理解AQS之CyclicBarrier&ReentrantReadWriteLock详解-fox 12-深入理解AQS之ReentrantReadWriteLock详解-fox ...

    彻底理解Java中的各种锁.pdf

    在Java中,ReentrantReadWriteLock类是读写锁的实现,它包含两个锁:读锁(共享锁)和写锁(独占锁)。读锁可以被多个线程同时持有,而写锁是独占的,当写锁被占用时,其他线程既不能获取读锁也不能获取写锁。 5. ...

    contention-profiling:ReentrantLock 和 ReentrantReadWriteLock 上的配置文件争用

    总之,`ReentrantLock`和`ReentrantReadWriteLock`提供了比`synchronized`更精细的锁控制,而争用分析则帮助我们理解和优化这些锁的使用。`contention-profiling-master`这个项目很可能是用来演示如何进行此类分析的...

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

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

    Java分布式应用学习笔记06浅谈并发加锁机制分析

    总结来说,Java中的并发加锁机制非常灵活且强大,通过对`ReentrantLock`和`ReentrantReadWriteLock`等工具的理解和应用,可以有效地解决多线程环境下的并发控制问题。希望本文能帮助读者更好地理解和掌握Java并发...

    2018最新华为小米面试题

    3. Lock接口:了解ReentrantLock、ReentrantReadWriteLock等高级锁的使用。 六、IO流 1. 文件操作:理解File类的基本操作,如创建、删除、重命名文件。 2. 字节流与字符流:掌握InputStream、OutputStream、Reader...

    2021年Java大厂面试题整理大全

    3. Lock接口:了解ReentrantLock、ReentrantReadWriteLock的使用,与synchronized的区别。 五、IO与NIO 1. 流:掌握InputStream、OutputStream、Reader、Writer及其子类的使用。 2. NIO:理解非阻塞I/O的特点,...

    homework-ReadWriteLock-KristampsW-main.zip

    - `ReentrantReadWriteLock`的源码提供了深入理解其工作原理的机会。例如,`readLock()`返回的`Sync`子类`NonReentrantReadLock`实现了读锁的逻辑,而`writeLock()`返回的`Sync`子类`NonReentrantWriteLock`实现了...

    java面试——北京-京东-Java中级.zip

    - Lock接口:ReentrantLock、ReentrantReadWriteLock等高级锁的使用。 5. **异常处理**: - 异常分类:了解检查异常和运行时异常的区别。 - try-catch-finally:理解异常捕获和处理,finally块的执行情况。 - ...

    Java的两种读写锁介绍

    在Java并发编程中,读写锁是用于优化多线程访问共享资源的一种机制,它可以提高对数据的并发访问效率。本文将深入探讨Java中的两种读写锁...理解并正确使用这些锁机制,能够帮助我们编写出更加高效、安全的多线程程序。

    reader and writer

    首先,我们需要理解读者-写者问题的核心是读写锁。在Java中,我们可以使用`java.util.concurrent.locks`包下的`ReentrantReadWriteLock`类来实现。这个锁提供了一种方式,使得多个线程可以同时读取数据,但只允许一...

    Java面试宝典--牛客网.zip

    4. Lock接口:熟悉ReentrantLock、ReentrantReadWriteLock等高级锁的使用。 四、IO与NIO 1. 流的概念:掌握字节流和字符流,以及其对应的输入输出类。 2. 文件操作:熟练进行文件的创建、读写、复制、删除等操作。 ...

    【并发编程】简单化理解AQS和ReentrantLock.pdf

    - **使用更高效的同步工具**:选择合适的数据结构和算法,比如使用`StampedLock`代替`ReentrantReadWriteLock`等。 #### 6. 错误处理 - **异常安全**:确保并发程序在出现异常时能够正确处理资源释放等问题。 - **...

    java学习内容[文].pdf

    4. 并发包:分析java.util.concurrent中的并发工具类,如ConcurrentHashMap、ThreadPoolExecutor、FutureTask、Semaphore、Condition、ReentrantReadWriteLock等。 5. JVM:深入理解JVM的工作机制,包括代码的编译、...

    java面试大集合

    3. Lock接口:ReentrantLock、ReentrantReadWriteLock的使用。 4. 线程池:ExecutorService、ThreadPoolExecutor、Future接口的理解与使用。 六、IO流与NIO 1. 流的分类:字节流、字符流、输入流、输出流。 2. 文件...

    读者写者公平操作

    在计算机科学和操作系统领域,"读者写者公平操作"是一个经典的多线程...理解和熟练应用这一机制对于开发高并发的Java应用程序至关重要。在实际项目中,根据具体需求选择公平或非公平策略,以达到性能和公平性的平衡。

Global site tag (gtag.js) - Google Analytics