Java---ReentrantReadWriteLock

25 篇文章 0 订阅
11 篇文章 0 订阅

一.概述

ReentrantReadWriteLock是在AQS的基础上实现的一个可重入锁。该锁具备重入锁的可重入性可中断获取锁等特征,但是与ReentrantLock不一样的是,它的内部维护了一把读锁和一把写锁读锁是共享锁写锁是排他锁。这样就保证了写数据时的线程安全性,又保证了读数据时的多线程并发,比较适合读取数据较多而写数据较少的并发场景。写锁是独占的,写时不能有其他线程写也不能读;所有的独锁都释放完之前也不能加写锁。

ReentrantReadWriteLock实现了ReadWriteLock接口,其中在ReentrantReadWriteLock中分别声明了以下几个静态内部类:

  • WriteLockReadLock(维护的一对读写锁):单从类名我们可以看出这两个类的作用,就是控制读写线程的锁
  • Sync及其子类NofairSyncFairSyncReentrantReadWriteLock(读写锁)是支持公平锁与非公平锁的。
  • Sync中的ThreadLoclHoldCounterHoldCounter:涉及到锁的重进入。

二.使用示例

使用ReentrantReadWriteLock进行TreeMap的读写操作。

import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class ReentrantReadWriteLockTest {
    private final Map<String, String> m = new TreeMap<String, String>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();//获取读锁
    private final Lock w = rwl.writeLock();//获取写锁

    //读取Map中的对应key的数据
    public String get(String key) {
        r.lock();
        try {
            return m.get(key);
        } finally {
            r.unlock();
        }
    }
    //往Map中写数据
    public String put(String key, String value) {
        w.lock();
        try {
            return m.put(key, value);
        } finally {
            w.unlock();
        }
    }
}

三.原理

1.读写锁需要维护3个数据:

  • 写锁的可重入次数;
  • 读锁的个数;
  • 读锁的可重入次数。

那既然需要维护三个数据,,同步队列中只有一个int类型的state变量来表示当前的同步状态。那么其内部是怎么将两个读写状态分开,并且达到控制线程的目的的呢?

ReentrantReadWriteLock中的同步队列,其实是将同步状态分为了两个部分,其中高16位表示读状态(所有读线程获取锁次数)低16位表示写状态,具体情况如下图所示:

这里大家需要注意的是,在实际的情况中,读状态与写状态是不能被不同线程同时赋值的。因为根据ReentrantReadWriteLock的设计来说,读写操作线程是互斥的。上图中这样表示,只是为了帮助大家理解同步状态的划分

获取读写状态:

  • 读状态:想要获取读状态,只需要将当前同步变量无符号右移16位
  • 写状态:我们只需要将当前同步状态(这里用S表示)进行这样的操作S&0x0000FFFF),也就是S&(1<<16-1)
  • state=0时,读线程和写线程都不持有锁。
  • state!=0sharedCount(c)!=0时表示读线程持有锁。
  • state!=0exclusiveCount(c)!=0时表示写线程持有锁。

四.HoldCounter和ThreadLocalHoldCounter

取出state高16位的对应的数值表示是所有线程获得读锁的次数,但是如何获得单个线程获得共享锁的次数呢?内部类Sync为同步器维护了一个读锁计数器,专门统计每个线程获得读锁的次数。Sync内部有两个内部类分别为HoldCounter和ThreadLocalHoldCounter:

abstract static class Sync extends AbstractQueuedSynchronizer {
    
    static final class HoldCounter {
        //计数器,用于统计线程重入读锁次数
        int count = 0;
        // Use id, not reference, to avoid garbage retention
        //线程TID,区分线程,可以唯一标识一个线程
        final long tid = getThreadId(Thread.currentThread());
    }
    
    static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
        //重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }
    
    private transient ThreadLocalHoldCounter readHolds;
   
    private transient HoldCounter cachedHoldCounter;
   
    private transient Thread firstReader = null;
    private transient int firstReaderHoldCount;
 
    Sync() {
        //本地线程读锁计数器
        readHolds = new ThreadLocalHoldCounter();
        setState(getState()); // ensures visibility of readHolds
    }
}
  • firstReader和firstReaderHoldCount
      如果只有一个线程获取了读锁,就不需要使用本地线程变量readHolds,当前线程就是第一个获得读锁的线程firstReader,使用firstReaderHoldCount存储线程重入次数。
  • readHolds
      第一个获得读锁的线程使用firstReaderHoldCount存储读锁重入次数,后面的线程就要使用ThreadLocal类型变量readHolds了,每个线程拥有自己的副本,用来保存自己的重入数。
  • cachedHoldCounter
      缓存计数器,是最后一个获取到读锁的线程计数器,每当有新的线程获取到读锁,这个变量都会更新。如果当前线程不是第一个获得读锁的线程,先到缓存计数器cachedHoldCounter查看缓存计数器是否指向当前线程,不是再去readHolds查找,通过缓存提高效率。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值