ReentrantReadWriteLock读写锁

Posted xieyanke

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ReentrantReadWriteLock读写锁相关的知识,希望对你有一定的参考价值。

使用分析

  四种读写情况:读读 读写 写读  写写, 接下来分四种情况看最终结果

情况一:读读使用

public static void main(String[] args) throws InterruptedException {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    new Thread( ()->{
        readLock.lock();
        try {
            System.out.println("t1 try get readLock");
            Thread.sleep(2000L);
            System.out.println("t1 got readLock");
        }catch (Exception e){

        }finally {
            readLock.unlock();
            System.out.println("t1 relase readLock");
        }

    }, "t1").start();

    new Thread( ()->{
        readLock.lock();
        try {
            System.out.println("t2 try get readLock");
            Thread.sleep(1000L);
            System.out.println("t2 got readLock");
        }catch (Exception e){

        }finally {
            readLock.unlock();
            System.out.println("t2 relase readLock");
        }

    }, "t2").start();

}

运行结果:
t1 try get readLock
t2 try get readLock
t2 got readLock
t2 relase readLock
t1 got readLock
t1 relase readLock

从运行结果可知: 读读非互斥

情况二:读写使用

public static void main(String[] args) throws InterruptedException {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    new Thread( ()->{
        readLock.lock();
        try {
            System.out.println("t1 try get readLock");
            Thread.sleep(2000L);
            System.out.println("t1 got readLock");
        }catch (Exception e){

        }finally {
            readLock.unlock();
            System.out.println("t1 relase readLock");
        }

    }, "t1").start();

    new Thread( ()->{
        writeLock.lock();
        try {
            System.out.println("t2 try get writeLock");
            Thread.sleep(1000L);
            System.out.println("t2 got writeLock");
        }catch (Exception e){

        }finally {
            writeLock.unlock();
            System.out.println("t2 relase writeLock");
        }

    }, "t2").start();

}
打印结果:
t1 try get readLock
t1 got readLock
t1 relase readLock
t2 try get writeLock
t2 got writeLock
t2 relase writeLock

从运行结果可知: 读写互斥

情况三:写读使用

public static void main(String[] args) throws InterruptedException {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    new Thread( ()->{
        writeLock.lock();
        try {
            System.out.println("t1 try get writeLock");
            Thread.sleep(2000L);
            System.out.println("t1 got writeLock");
        }catch (Exception e){

        }finally {
            writeLock.unlock();
            System.out.println("t1 relase writeLock");
        }

    }, "t1").start();

    new Thread( ()->{
        readLock.lock();
        try {
            System.out.println("t2 try get readLock");
            Thread.sleep(1000L);
            System.out.println("t2 got readLock");
        }catch (Exception e){

        }finally {
            readLock.unlock();
            System.out.println("t2 relase readLock");
        }

    }, "t2").start();

}
打印结果:
t1 try get writeLock
t1 got writeLock
t1 relase writeLock
t2 try get readLock
t2 got readLock
t2 relase readLock

从运行结果可知: 写读互斥

情况四:写写使用

public static void main(String[] args) throws InterruptedException {

    ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();

    new Thread( ()->{
        writeLock.lock();
        try {
            System.out.println("t1 try get writeLock");
            Thread.sleep(2000L);
            System.out.println("t1 got writeLock");
        }catch (Exception e){

        }finally {
            writeLock.unlock();
            System.out.println("t1 relase writeLock");
        }

    }, "t1").start();

    new Thread( ()->{
        writeLock.lock();
        try {
            System.out.println("t2 try get writeLock");
            Thread.sleep(1000L);
            System.out.println("t2 got writeLock");
        }catch (Exception e){

        }finally {
            writeLock.unlock();
            System.out.println("t2 relase writeLock");
        }

    }, "t2").start();

}
打印结果:
t1 try get writeLock
t1 got writeLock
t1 relase writeLock
t2 try get writeLock
t2 got writeLock
t2 relase writeLock

从运行结果可知: 写写互斥

使用总结: 除读读共享锁,其他情况表现为互斥

源码分析

构造方法分析

  以下代码可以看出ReadLock WriteLock都是依赖ReentrantReadWriteLock类的同步器进行锁操作, 下面重点关注ReentrantReadWriteLock类中Sync的代码

ReentrantReadWriteLock类
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    //读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    //写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    //同步器 获取锁和释放锁具体实现类
    final Sync sync;

    //默认构造器
    public ReentrantReadWriteLock() {
        this(false);
    }

    //实际构造器
    //初始化上面三个参数 readerLock writerLock  sync
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }
}

ReadLock类
public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;

    //使用ReentrantReadWriteLock.sync赋值给ReadLock.sync
    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
}

WriteLock类
public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;

    //使用ReentrantReadWriteLock.WriteLock.sync
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }
}

Sync类

abstract static class Sync extends AbstractQueuedSynchronizer {

    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;

    //读锁重入数
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    //写锁重入数
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }


    //获取当前线程重入次数
    //这里比较难理解, ThreadLocalHoldCounter继承自ThreadLocal,每一个线程都有自己重入次数
    //这里的作用是将重入次数放在线程缓存中, 当对应执行线程需要获取重入次数时只需要使用get()方法
    private transient ThreadLocalHoldCounter readHolds;

   
    //最后一个获取读锁的线程
    //举例:读读共享锁, 可能存在若干读锁, 读(fist) 读 读 读(last), cachedHoldCounter记录的是last线程读锁重入次数
    private transient HoldCounter cachedHoldCounter;

    
    //第一个获取读锁的线程
    private transient Thread firstReader = null;
    //第一个获取读锁的线程获取读锁重入数
    private transient int firstReaderHoldCount;
}

 

读写锁的核心是把AQS中的state设置为一个32位的值, 高16位表示读锁重入次数, 低16位表示写锁重入次数

读锁获取: 1 若线程需要进入CLH等待队列, 则state的值不变, 例:读写 写读

                2若是锁持有线程为读锁, 且写状态为0,此时需要修改state的值, 例 读

读锁释放: state减去 1<<16 

写锁获取: 1若持有线程为当前线程state加1(因为写状态为低位直接加1就行) 

                 2若非持有线程,则需要判断锁没有被其他线程持有,state加1

写锁释放: state减去1

技术图片

1 获取读锁

public void lock() {
    sync.acquireShared(1);
}
//获取共享锁
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
//尝试获取共享锁返回值-1表示线程需要加入CLH队列等待 否则放行
protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    //如果排他锁(写锁)被其他线程(非当前线程),返回失败
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    //获取共享锁(读锁)状态 持有锁的线程共享锁重入次数
    int r = sharedCount(c);
    
    if (
        //核心逻辑判断是第一个判断readerShouldBlock, 判断读锁是否需要阻塞 
        //公平模式判断需要入列  :存在CLH队列,并且当前锁占有线程非当前线程
        //非公平模式判断需要入列:增加首节点是否为写锁等待,优先写锁原则,其他类似公平模式
        !readerShouldBlock() 
        //重入个数未到最大值
        && r < MAX_COUNT 
        //CAS修改值
        && compareAndSetState(c, c + SHARED_UNIT)) {
        //---进入此处代码段则表示锁获取成功 以下代码作用是辅助属性的赋值

        //r==0读锁持有者重入次数为0
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } 
        //如果首个读节点为当前节点 表示重入 计数+1
        else if (firstReader == current) {
            firstReaderHoldCount++;
        } 
        //等价于 r!=0 且 读锁持有线程非当前线程
        //读读共享锁
        else {
            //设置重入次数
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //补偿获取共享锁
    return fullTryAcquireShared(current);
}

2 释放读锁

/** ReentrantReadWriteLock.ReadLock 类 */
public void unlock() {
    sync.releaseShared(1);
}


/** AbstractQueuedSynchronizer类 */
public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

/** ReentrantReadWriteLock.ReadLock 类 */
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    //如果第一个读锁为当前锁
    if (firstReader == current) {
        //线程只获取一次读锁 则将firstReader置为空
        if (firstReaderHoldCount == 1)
            firstReader = null;
        //线程有重入获取读锁 则将重入数减一
        else
            firstReaderHoldCount--;
    } 
    //如果当前线程非第一个读锁获取者 则从线程缓存中(ThreadLocal原理)中获取重入数, 
    //然后执行清除缓存(未重入) 或者 重入次数减一(有重入)
    else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    //无限循环尝试compareAndSetState (CAS操作) 直到成功
    //因为读锁为共享锁 在执行CAS操作的同时可能会存在别的获取读锁线程正在获取或释放锁
    //所以有可能失败的 就需要再次尝试CAS操作
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // Releasing the read lock has no effect on readers,
            // but it may allow waiting writers to proceed if
            // both read and write locks are now free.
            return nextc == 0;
    }
}

 

3 获取写锁

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//获取锁
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
//尝试获取锁
protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    //如果c!=0则存在读锁或写锁
    if (c != 0) {
        //如果写锁等于0,说明读锁不等于0, 若持有线程非当前线程 失败
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        //超过最大写锁设定值 抛出异常
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        //代码能走到这里 说明写锁不等于0, 且持有当前线程, 锁重入
        setState(c + acquires);
        return true;
    }
    //以上代码必然直接返回结果, 代码走到这里说明c==0,不存在读写锁持有情况
    if (
        //判断当前线程获取写锁是否需要入CLH等待队列
        //writerShouldBlock()方法代码很简单,只是CLH队列是否有等待的线程
        //公平模式下如果有等待返回false(不等待),非公平模式直接返回false(不等待)
        writerShouldBlock() ||
        //不需要需要写锁进入CLH, 然后修改AQS state值失败, 则返回false
        !compareAndSetState(c, c + acquires))
        return false;
    //代码走到这前提 1不需要入CLH队列等待 2修改AQS state成功 直接设置锁占用线程为当前线程即可
    setExclusiveOwnerThread(current);
    return true;
}

4 释放写锁

//释放锁
public final boolean release(int arg) {
    //尝试释放锁 并判断重入次数是否全部释放完成
    //若全部重入释放完成 则unpark CLH等待队列的第一个节点
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}
//尝试释放
protected final boolean tryRelease(int releases) {
    //持锁线程非当前线程 抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //状态计算
    int nextc = getState() - releases;
    //判断重入情况 
    boolean free = exclusiveCount(nextc) == 0;
    //若重入为0 表示此线程可以让出占用
    if (free)
        setExclusiveOwnerThread(null);
    //状态设置
    setState(nextc);
    //返回是否释放线程
    return free;
}

附:

1 高低位增加一次重入时AQS状态值的变更:

  假设有十进制数字 11115555 如果说希望高4位用来做读锁值 低四位用来做写锁值,

  那么增加一个读锁之后期望的到的结果是11125555,通过减法11125555-11115555=10000可知, 高位如果需要加1则实际需要加10000

  同理增加一个写锁之后期望的到的结果是11115556,通过减法11115556-11115555=1可知,低位直接按实际值增加即可 

  回到AQS增加读锁重入时 state = state+(1<<16) , AQS增加写锁重入时 state = state+1

2 函数获取读锁数 int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

  这个应该比较好理解, 就是将原有的32位向右移16位, 那么低(右侧)消失, 举个例子 1111 1111 0000 000 向右移8位变成 1111 1111

3 函数获取写锁数 int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

  需要掌握一些按位与的知识, EXCLUSIVE_MASK = 0000 0000 0000 0000 1111 1111 1111 1111 

  那么  c & EXCLUSIVE_MASK 表示同时为1 结果为1 否则为0,   

  假定 c = 0000 0000 0000 0001 0000 0000 0000 0010 表示读状态为1 写状态为2

  0000 0000 0000 0000 1111 1111 1111 1111

  &

  0000 0000 0000 0001 0000 0000 0000 0010

      --------------------------------------------------------------------------

   =  0000 0000 0000 0000 0000 0000 0000 0010

  c & EXCLUSIVE_MASK 得到的最终结果 0000 0000 0000 0000 0000 0000 0000 0010, 从而筛选出写状态值为2

 

以上是关于ReentrantReadWriteLock读写锁的主要内容,如果未能解决你的问题,请参考以下文章

[图解Java]读写锁ReentrantReadWriteLock

JUC中的读写锁(ReentrantReadWriteLock)

读写锁 ReentrantReadWriteLock

ReentrantReadWriteLock读写锁的使用

AQS系列- ReentrantReadWriteLock读写锁的加锁

ReentrantReadWriteLock读写锁