`

ReentrantReadWriteLock可重入读写锁分析

    博客分类:
  • Java
 
阅读更多



ReentrantReadWriteLock 可重入的读写锁

什么叫可重入:就是同一个线程可以重复加锁,可以对同一个锁加多次,每次释放的时候回释放一次,直到该线程加锁次数为0,这个线程才释放锁。

什么叫读写锁: 也就是读锁可以共享,多个线程可以同时拥有读锁,但是写锁却只能只有一个线程拥有,而且获取写锁的时候,其他线程都已经释放了读锁,而且在该线程获取写锁之后,其他线程不能再获取读锁。



我们先看下下面两个示例: ReentrantReadWriteLock.java自带的两个示例





Java代码 
1.* class CachedData { 
2.*   Object data; 
3.*   volatile boolean cacheValid; 
4.*   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
5.* 
6.*   void processCachedData() { 
7.*     rwl.readLock().lock(); 
8.*     if (!cacheValid) { 
9.*        // Must release read lock before acquiring write lock 
10.*        rwl.readLock().unlock(); 
11.*        rwl.writeLock().lock(); 
12.*        // Recheck state because another thread might have acquired 
13.*        //   write lock and changed state before we did. 
14.*        if (!cacheValid) { 
15.*          data = ... 
16.*          cacheValid = true; 
17.*        } 
18.*        // Downgrade by acquiring read lock before releasing write lock 
19.*        rwl.readLock().lock(); 
20.*        rwl.writeLock().unlock(); // Unlock write, still hold read 
21.*     } 
22.* 
23.*     use(data); 
24.*     rwl.readLock().unlock(); 
25.*   } 
26.* } 





如果要对cache的内容进行更新,则必须得加写锁,如果只是读取,则加读锁就可以了。









Java代码 
1.* class RWDictionary { 
2.*    private final Map<String, Data> m = new TreeMap<String, Data>(); 
3.*    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 
4.*    private final Lock r = rwl.readLock(); 
5.*    private final Lock w = rwl.writeLock(); 
6.* 
7.*    public Data get(String key) { 
8.*        r.lock(); 
9.*        try { return m.get(key); } 
10.*        finally { r.unlock(); } 
11.*    } 
12.*    public String[] allKeys() { 
13.*        r.lock(); 
14.*        try { return m.keySet().toArray(); } 
15.*        finally { r.unlock(); } 
16.*    } 
17.*    public Data put(String key, Data value) { 
18.*        w.lock(); 
19.*        try { return m.put(key, value); } 
20.*        finally { w.unlock(); } 
21.*    } 
22.*    public void clear() { 
23.*        w.lock(); 
24.*        try { m.clear(); } 
25.*        finally { w.unlock(); } 
26.*    } 
27.* }} 



这个示例,同样,对map进行get,allKeys读操作加读锁,put,clear操作加写锁。



我们先看下ReentrantReadWriteLock这个类的两个构造函数:







Java代码 
1.public ReentrantReadWriteLock() { 
2.    this(false); 
3.} 
4. 
5./**
6. * Creates a new {@code ReentrantReadWriteLock} with
7. * the given fairness policy.
8. *
9. * @param fair {@code true} if this lock should use a fair ordering policy
10. */ 
11.public ReentrantReadWriteLock(boolean fair) { 
12.    sync = (fair)? new FairSync() : new NonfairSync(); 
13.    readerLock = new ReadLock(this); 
14.    writerLock = new WriteLock(this); 
15.} 





fair这个参数表示是否是创建一个公平的读写锁,还是非公平的读写锁。也就是抢占式还是非抢占式。



公平和非公平:公平表示获取的锁的顺序是按照线程加锁的顺序来分配获取到锁的线程时最先加锁的线程,是按照FIFO的顺序来分配锁的;非公平表示获取锁的顺序是无需的,后来加锁的线程可能先获得锁,这种情况就导致某些线程可能一直没获取到锁。



公平锁为啥会影响性能,从code上来看看公平锁仅仅是多了一项检查是否在队首会影响性能,如不是,那么又是在什么地方影响的?假如是闯入的线程,会排在队尾并睡觉(parking)等待前任节点唤醒,这样势必会比非公平锁添加很多paking和unparking的操作



一般的应用场景是: 如果有多个读线程,一个写线程,而且写线程在操作的时候需要阻塞读线程,那么此时就需要使用公平锁,要不然可能写线程一直获取不到锁,导致线程饿死。



我这边在一个项目就用到了读写锁:



一个KV引擎的java客户端:java客户端需要缓存KV引擎服务器的集群配置信息(master server,data node service)多个map的数据结构;更新时由后台一个写线程定时更新;而读取则同时可能几十个线程同时读,因为需要读取配置定时定位到一个key对应的data nodeserver去获取数据;为了更好的避免这些缓存的数据读写同步导致的问题,所以使用读写锁来解决同步的问题。读的时候加读锁,写的时候加写锁,在写的过程中需要阻塞读,因为写的过程非常快,所以可以阻塞读。关键是写的过程不能让读线程读取到一部分数据是旧的,一部分是新的,导致获取结果失败甚至出错。而这种case显然是读线程多,写线程只有一个,在测试过程中就发现写线程一直获取不到锁,因为用的是非公平锁,所以后来通过查询API,使用公平锁就可以了。





下面我们来看下具体读写锁的实现:



获取读锁的过程:







Java代码 
1.protected final int tryAcquireShared(int unused) { 
2.    /*
3.     * Walkthrough:
4.     * 1. If write lock held by another thread, fail
5.     * 2. If count saturated, throw error
6.     * 3. Otherwise, this thread is eligible for
7.     *    lock wrt state, so ask if it should block
8.     *    because of queue policy. If not, try
9.     *    to grant by CASing state and updating count.
10.     *    Note that step does not check for reentrant
11.     *    acquires, which is postponed to full version
12.     *    to avoid having to check hold count in
13.     *    the more typical non-reentrant case.
14.     * 4. If step 3 fails either because thread
15.     *    apparently not eligible or CAS fails,
16.     *    chain to version with full retry loop.
17.     */ 
18.    Thread current = Thread.currentThread(); 
19.    int c = getState(); 
20.    if (exclusiveCount(c) != 0 && 
21.        getExclusiveOwnerThread() != current) 
22.        return -1; 
23.    if (sharedCount(c) == MAX_COUNT) 
24.        throw new Error("Maximum lock count exceeded"); 
25.    if (!readerShouldBlock(current) && 
26.        compareAndSetState(c, c + SHARED_UNIT)) { 
27.        HoldCounter rh = cachedHoldCounter; 
28.        if (rh == null || rh.tid != current.getId()) 
29.            cachedHoldCounter = rh = readHolds.get(); 
30.        rh.count++; 
31.        return 1; 
32.    } 
33.    return fullTryAcquireShared(current); 
34.}        /**
35. * Full version of acquire for reads, that handles CAS misses
36. * and reentrant reads not dealt with in tryAcquireShared.
37. */ 
38.final int fullTryAcquireShared(Thread current) { 
39.    /*
40.     * This code is in part redundant with that in
41.     * tryAcquireShared but is simpler overall by not
42.     * complicating tryAcquireShared with interactions between
43.     * retries and lazily reading hold counts.
44.     */ 
45.    HoldCounter rh = cachedHoldCounter; 
46.    if (rh == null || rh.tid != current.getId()) 
47.        rh = readHolds.get(); 
48.    for (;;) { 
49.        int c = getState(); 
50.        int w = exclusiveCount(c); 
51.        if ((w != 0 && getExclusiveOwnerThread() != current) || 
52.            ((rh.count | w) == 0 && readerShouldBlock(current))) 
53.            return -1; 
54.        if (sharedCount(c) == MAX_COUNT) 
55.            throw new Error("Maximum lock count exceeded"); 
56.        if (compareAndSetState(c, c + SHARED_UNIT)) { 
57.            cachedHoldCounter = rh; // cache for release 
58.            rh.count++; 
59.            return 1; 
60.        } 
61.    } 
62.} 
  


如果写锁已经被获取了,则获取读锁失败;如果当前线程重入加锁次数达到MAX_COUNT,获取读锁失败;readerShouldBlock如果是公平锁,则判断当然线程是否是排在队列前面,如果不是,则等待,是则获取读锁;可重入性是通过class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> threadlocal来保存重入获取锁的次数;然后就是调用fullTryAcquireShared方法对当前线程获取锁的次数进行操作。





获取写锁的过程:



  公平锁获取锁的过程:

    /**



Java代码 
1.* Fair version of tryAcquire.  Don't grant access unless 
2.* recursive call or no waiters or is first. 
3.*/ 
4.rotected final boolean tryAcquire(int acquires) { 
5.   final Thread current = Thread.currentThread(); 
6.   int c = getState(); 
7.   if (c == 0) { 
8.       if (isFirst(current) && 
9.           compareAndSetState(0, acquires)) { 
10.           setExclusiveOwnerThread(current); 
11.           return true; 
12.       } 
13.   } 
14.   else if (current == getExclusiveOwnerThread()) { 
15.       int nextc = c + acquires; 
16.       if (nextc < 0) 
17.           throw new Error("Maximum lock count exceeded"); 
18.       setState(nextc); 
19.       return true; 
20.   } 
21.   return false; 
  
  判断当前线程是否是等待队列的头线程,如果是,则把当前的排斥锁线程设置为当前线程,获取写锁成功;否则判断当前写锁的线程是不是当前线程,如果是则对写锁的重入数量进行加1操作;否则获取写锁失败。



非公平的写锁获取过程:







Java代码 
1.final boolean nonfairTryAcquire(int acquires) { 
2.           final Thread current = Thread.currentThread(); 
3.           int c = getState(); 
4.           if (c == 0) { 
5.               if (compareAndSetState(0, acquires)) { 
6.                   setExclusiveOwnerThread(current); 
7.                   return true; 
8.               } 
9.           } 
10.           else if (current == getExclusiveOwnerThread()) { 
11.               int nextc = c + acquires; 
12.               if (nextc < 0) // overflow 
13.                   throw new Error("Maximum lock count exceeded"); 
14.               setState(nextc); 
15.               return true; 
16.           } 
17.           return false; 
18.       } 
  
跟公平写锁获取过程不同的是没有判断当前线程是否是等待队列线程的第一个。


分享到:
评论

相关推荐

    java 读写锁代码

    - `ReentrantReadWriteLock`是可重入的,意味着一个线程可以多次获取同一类型的锁(读或写),只要它能正确释放。 - 它提供了`ReadLock`和`WriteLock`接口,分别代表读锁和写锁,可以通过`lock()`和`unlock()`方法...

    Java的两种读写锁介绍

    一、ReentrantReadWriteLock(可重入读写锁) 1. **简介**: ReentrantReadWriteLock是Java并发包`java.util.concurrent.locks`中的一个类,它提供了比`synchronized`关键字更灵活的锁机制。它允许多个读线程同时...

    Java 读写锁实现原理浅析

    ReentrantReadWriteLock 是 Java 中的一个读写锁实现,它提供了公平性选择、可重入、可降级等特性。ReentrantReadWriteLock 的核心是由一个基于 AQS 的同步器 Sync 组成,然后由其扩展出 ReadLock(共享锁)和 ...

    Java多线程 ReentrantReadWriteLock原理及实例详解

    - **可重入性**:与ReentrantLock类似,ReentrantReadWriteLock支持锁的可重入特性,即线程可以多次获取同一类型的锁(读锁或写锁),而不会被自己阻塞。 - **公平性**:ReentrantReadWriteLock可以设置为公平模式...

    homework-ReadWriteLock-KristampsW-main.zip

    - `ReentrantReadWriteLock`是Java提供的可重入的读写锁实现,它继承自`AbstractQueuedSynchronizer`(AQS)。可重入意味着一个线程可以多次获取同一锁,这在递归调用或者锁嵌套时很有用。 - **公平性**:`...

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

    `ReentrantLock`是一种可重入的互斥锁,支持公平和非公平两种模式。它内部有一个`Sync`类,继承自`AbstractQueuedSynchronizer`,并且有两个子类分别实现了公平和非公平锁的逻辑。 - **非公平锁** (`NonfairSync`):...

    MarkWord 锁标记1

    在Java并发编程中,MarkWord是对象头的一部分,它...- **读写分离**:使用读写锁(ReentrantReadWriteLock)来提高读操作的并发性。 理解并合理运用这些锁状态和优化策略,对于编写高效、低冲突的并发代码至关重要。

    并发编程过程中常用的原子锁和场景解答和模拟

    2. **可重入锁(ReentrantLock)**:是Java `java.util.concurrent.locks`包提供的锁,它具有与内置锁相似的功能,但提供了更高级的特性,如公平锁、非公平锁、尝试锁、定时锁和条件变量等。 3. **读写锁...

    java多线程、锁的教程跟案例

    - ReentrantLock(可重入锁):提供与synchronized相似的功能,但更灵活,支持公平锁、非公平锁,以及可中断和定时尝试获取锁。 - ReadWriteLock(读写锁):ReentrantReadWriteLock提供了读写分离的锁,允许多个...

    【BAT必备】并发编程锁面试题

    - **ReentrantLock类**:这是一个可重入的互斥锁,比synchronized更灵活,提供了一系列高级功能。 - **ReentrantReadWriteLock类**:提供了一种读写分离的锁机制,允许多个读操作同时进行但不允许读写或写写并发。 ...

    panda-demo.zip

    - **实现**:Java中`ReentrantReadWriteLock`是ReadWriteLock的实现,它具有可重入性,即一个线程可以获取同一锁多次。 - **组成部分**:读写锁由两把锁组成:读锁(ReadLock)和写锁(WriteLock)。读锁可以被多...

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

    4. **锁的并发性**:检查读写锁的并发使用情况,确保读多写少的优势得到发挥。 通过对这些指标的分析,我们可以识别出代码中的热点,并对锁的使用进行优化,比如使用锁分离、减少锁粒度、使用条件变量等策略,来...

    JUC并发编程与源码分析视频课.zip

    9. **锁的高级特性**:包括读写锁(ReentrantReadWriteLock)、乐观锁(StampedLock)以及锁的可重入性、公平性和非公平性等概念。 10. **源码分析**:通过对JUC库中部分关键类的源码分析,帮助学员深入理解并发...

    one lock.zip

    在实际编程中,Java有`java.util.concurrent.locks`包提供了这些锁的实现,如`ReentrantLock`(可重入锁,互斥锁的一种),`ReadWriteLock`接口及其实现类`ReentrantReadWriteLock`,以及`Semaphore`类。C++的`std::...

    基于JDK源码解析Java领域中的并发锁之设计与实现.pdf

    典型的Lock实现如ReentrantLock,它支持公平锁和非公平锁,以及可重入和可中断的特性。 五、ReadWriteLock接口的设计与实现 ReadWriteLock接口代表读写锁,它允许多个读取者同时访问资源,但在写入时确保互斥。典型...

    读者与写着问题2源代码

    4. **读写锁**:读写锁是解决这个问题的关键工具。读写锁允许多个读者同时持有读锁,但只有单一的写者可以持有写锁。在Java中,`ReentrantReadWriteLock`提供了读锁和写锁的粒度控制,可以有效避免死锁。 5. **信号...

    并发编程面试专题1

    - **可重入性**:Lock支持线程的可重入,比如`ReentrantLock`。 - **更灵活的锁定**:可以显式地获取和释放锁,而不是依赖于块或方法的退出。 - **尝试获取锁**:`tryLock()`方法允许非阻塞地尝试获取锁。 - **...

Global site tag (gtag.js) - Google Analytics