Java8 ReentrantReadWriteLock 源码解析

目录

一、定义

二、使用

三、WriteLock

1、lock / lockInterruptibly 

2、tryLock

3、unlock

四、ReadLock

1、lock 

 2、lockInterruptibly 

3、tryLock

4、unlock

五、NonfairSync /  FairSync

六、AbstractQueuedSynchronizer实现总结


ReentrantReadWriteLock是一个可重入的,支持读写分离的锁,本篇博客就详细探讨该类的实现。

一、定义

    ReentrantReadWriteLock用于实现ReadWriteLock接口,该接口定义的方法如下:

该类包含多个内部类,如下:

ReadLock和WriteLock两个都是基于Sync实现的Lock接口,只是调用Sync的方法不同而已。另外两个类FairSync和NonfairSync都继承自Sync,分别表示公平锁和非公平锁,其类继承关系如下:

 FairSync和NonfairSync都是只是实现了父类的两个抽象方法,writerShouldBlock和readerShouldBlock方法,这两方法用于判断写操作是否需要阻塞,读操作是否需要阻塞。

Sync内部还有两个内部类,其定义如下:

HoldCounter是一个数据结构用来记录每个线程的重入读锁次数和关联的线程ID, ThreadLocalHoldCounter表示一个存储HoldCounter实例的本地线程变量。多个线程可同时获取读锁,他们获取的读锁实际都是同一个Sync实例,Sync只有一个state属性用来保存重入读锁的总次数,当该次数变成0的时候才能真正释放读锁。因为获取读锁的线程都需要执行unlock方法释放锁,所以必须记录每个线程自身重入读锁的次数,使用ThreadLocalHoldCounter,即下面的readHolds属性来实现。

Sync中包含的属性如下:

//本地线程变量,保存当前线程累计重入读锁的次数
private transient ThreadLocalHoldCounter readHolds;
//缓存的上一次成功获取读锁的HoldCounter 
private transient HoldCounter cachedHoldCounter;
//第一个获取读锁的线程
private transient Thread firstReader = null;
//第一个获取读锁的线程,累计重入读锁的次数
private transient int firstReaderHoldCount;

除此之外,Sync定义了几个常量,用来计算重入读锁或者写锁的累计次数,如下:

因为一个线程在获取写锁后可以再获取读锁,为了在state属性中同时记录重入这两种锁的次数,Sync只有这一个属性保存锁重入次数,就将state属性拆成两半,高16位的数值用于表示读锁的重入次数,低16位的值表示写锁的重入次数,因为有位数限制,所以重入的最大次数不能超过上面的MAX_COUNT。

ReentrantReadWriteLock本身的属性如下:

//读锁实例
private final ReentrantReadWriteLock.ReadLock readerLock;
//写锁实例
private final ReentrantReadWriteLock.WriteLock writerLock;
//完成实际同步动作的Sync实例
final Sync sync;
private static final sun.misc.Unsafe UNSAFE;
//Thread类的tid属性的偏移量,该属性实际是关联的JVM中的JavaThread实例的指针
private static final long TID_OFFSET;

 两个static属性通过static代码块完成初始化,如下:

另外三个属性是在构造函数中初始化的,构造函数的实现如下:

public ReentrantReadWriteLock() {
        this(false);
    }

public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

 下面就以读锁和写锁的具体实现为入口来分析内部类Sync的相关实现,因为其底层都是基于AbstractQueuedSynchronizer,涉及的相关方法可以参考上一篇《Java8 ReentrantLock 源码解析》

二、使用

     ReentrantReadWriteLock的使用,核心还是基于其writeLock和readLock返回的写锁和读锁,跟ReentrantLock的使用是一样,如下:

@Test
    public void test() throws Exception {
        ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
        Lock readLock=lock.readLock();
        CyclicBarrier cyclicBarrier=new CyclicBarrier(6);
        Runnable a=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
                try {
                    readLock.lock();
                    Thread.sleep(1000);
                    System.out.println("do something end for read,time->"+System.currentTimeMillis());
                    //读锁时因为读锁是共享锁,其他线程都能成功获取锁,所以不会出现死锁问题
                    cyclicBarrier.await();
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    readLock.unlock();
                }

            }
        };
        for(int i=0;i<5;i++){
            new Thread(a).start();
        }
        long start=System.currentTimeMillis();
        cyclicBarrier.await();
        System.out.println("read end,time->"+(System.currentTimeMillis()-start));
        Lock writeLock=lock.writeLock();
        Runnable b=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
                try {
                    writeLock.lock();
                    Thread.sleep(1000);
                    System.out.println("do something end for write,time->"+System.currentTimeMillis());
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    //先unlock解锁,在await等待,避免形成死锁
                    writeLock.unlock();
                    try {
                        cyclicBarrier.await();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }

            }
        };
        for(int i=0;i<5;i++){
            new Thread(b).start();
        }
        start=System.currentTimeMillis();
        cyclicBarrier.await();
        System.out.println("write end,time->"+(System.currentTimeMillis()-start));
    }

上述用例的输出如下:

//5个读操作线程依次启动
Thread-0 start,time->1585987927568
Thread-1 start,time->1585987927568
Thread-3 start,time->1585987927568
Thread-2 start,time->1585987927568
Thread-4 start,time->1585987927569
//几乎是同一时间获取读锁,然后完成读操作
do something end for read,time->1585987928568
do something end for read,time->1585987928569
do something end for read,time->1585987928569
do something end for read,time->1585987928569
do something end for read,time->1585987928569
//读锁是共享锁,整体耗时基本等于单个线程耗时,即sleep的耗时
read end,time->1018
//5个写操作线程依次启动
Thread-5 start,time->1585987928571
Thread-6 start,time->1585987928571
Thread-7 start,time->1585987928571
Thread-8 start,time->1585987928571
Thread-9 start,time->1585987928571
//写锁是排他锁,5个线程依次获取锁
do something end for write,time->1585987929571
do something end for write,time->1585987930571
do something end for write,time->1585987931571
do something end for write,time->1585987932571
do something end for write,time->1585987933571
//总耗时是5个写操作线程的累计耗时
write end,time->5000

 上述用例中读锁似乎没啥价值,因为不加锁的效果跟加读锁的一样。读锁的价值不在于共享,而在于可以排斥写锁,即获取了读锁以后就不能再获取写锁了,必须等读锁释放了才能获取读锁,测试用例如下:

@Test
    public void test2() throws Exception {
        ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
        Lock readLock=lock.readLock();
        Lock writeLock=lock.writeLock();
        CountDownLatch writeCount=new CountDownLatch(5);
        Runnable a=new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" start,time->"+System.currentTimeMillis());
                try {
                    readLock.lock();
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName()+" do something end for read,time->"+System.currentTimeMillis());
                    try{
                        //写锁必须等待所有的读锁释放才能获取锁,此处是读锁获取完成,等待获取写锁
                        //就形成了死锁
                        writeLock.lock();
                        Thread.sleep(1000);
                        System.out.println(Thread.currentThread().getName()+" do something end for write,time->"+System.currentTimeMillis());
                    }finally {
                        writeLock.unlock();
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }finally {
                    readLock.unlock();
                    writeCount.countDown();
                }

            }
        };
        for(int i=
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值