JUC 之 ReentrantReadWriteLock 读写锁

JUC 之 ReentrantReadWriteLock 读写锁

对于 ReentrantReadWriteLock 读写锁的实现,我们需要关注的是:如何处理 state 变量(如何通过一个单变量几表示写锁有表示读锁)以及读锁、写锁如何表示?下面我们就从这两方面入手开始学习。

核心成员介绍

private final ReentrantReadWriteLock.ReadLock readerLock; // 读锁实现

private final ReentrantReadWriteLock.WriteLock writerLock;// 写锁实现

final Sync sync; // 同步器实现,读写锁实现的核心均通过它来完成

public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync(); // 区分公平与非公平
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

先来看看 ReadLock 和 WriteLock 的内部实现

ReadLock 实现

通过源码我们发现内部通过 Sync 来实现。读锁通过 AQS 的 acquireShared 和 releaseShared 两个核心方法实现共享锁机制。这两个方法已在【AQS 之 共享锁】中详细讲解过。

public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;
 	// 获取锁,调用 sync 的 acquireShared 方法实现
    public void lock() {
        sync.acquireShared(1);
    }
	// 响应中断的方式获取锁,在等待过程中可被中断唤醒,调用 sync 的 acquireSharedInterruptibly 方法实现
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }  
    // 尝试获取读锁,获取不到立即返回,调用 sync 的 tryReadLock 方法实现
    public boolean tryLock() {
        return sync.tryReadLock();
    }
    // 带有等待超时的方式获取读锁,调用 sync 的 tryAcquireSharedNanos 方法
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    // 释放锁,调用 sync 的 releaseShared 方法
    public void unlock() {
        sync.releaseShared(1);
    }
}

WriteLock 实现

基于 AQS 的 互斥锁原理实现写锁,详解请看【AQS 之 互斥锁】一篇。

public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;
    // 获取写锁,调用用 sync 的 acquire 方法
    public void lock() {
        sync.acquire(1);
    }
    // 以可响应中断的方式获取写锁,调用用 sync 的 acquireInterruptibly 方法
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    // 尝试获取写锁,获取失败立即返回,调用 sync 的 tryWriteLock 方法
    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }
    // 尝试获取写锁,获取失败等待一段时间,调用 sync 的 tryAcquireNanos 方法,可被中断唤醒
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
    // 释放写锁,调用 sync 的 unlock 方法 
    public void unlock() {
        sync.release(1);
    }
    // 创建 Condition 条件对象
    public Condition newCondition() {
        return sync.newCondition();
    }
}

通过上述源码我们发现读写锁实现的核心均在 Sync 中,下面我们详细讲解 Sync 这个内部类

Sync 实现

Sync 继承自 AbstractQueuedSynchronizer (AQS),拥有 AQS 的所有信息。它是所有同步器的核心基石。我们来看看 Sync 是如何利用AQS 所提供的信息完成读写锁功能的。学习下面内容时,大家要了解二进制的与运算、或运算、异或运算等。这里给大家一个口诀:

与运算:二进制截断,也即有零则为零;

或运算:二进制组合,也即有 1 则为 1;

异或运算:无进位加法,也即相同为 0,相异为 1;

1、将整型 state 变量切割为 高低 各16 位,高 16 位用于记录读锁信息,低 16 位用于记录写锁信息,通过二进制的位运算来获取读写锁信息

2、使用 HoldCounter 记录每个线程读锁的重入次数, 使用 ThreadLocal(ThreadLocalHoldCounter) 保存所有的 HoldCounter

核心成员介绍
abstract static class Sync extends AbstractQueuedSynchronizer {
    static final int SHARED_SHIFT   = 16;
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);// 用于对读锁加 1 操作
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;// 写锁最大个数限制
    static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;// 掩码,用于快速计算写锁个数

    // 获取读锁个数,右移 16 位,取高 16 位的值
    static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }
    // 获取写锁个数,通过 与运算(&)将高 16 位截断,保留 低 16 位
    static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
	// 该类用于保存线程信息以及该线程的锁重入次数
    static final class HoldCounter {
        int count = 0;
        final long tid = getThreadId(Thread.currentThread());
    }
    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
    }
}
核心方法介绍
tryAcquire 方法

该方法由 AQS 负责回调,实现具体的获取锁操作,也即如何操作 state 变量。写锁的实现逻辑同 互斥锁。两者唯一区别在于互斥锁操作的是整个 state 变量,而写锁操作的是 state 变量的低 16 位。

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();// 保存当前 state 变量的值
    int w = exclusiveCount(c);// 获取写锁个数
    if (c != 0) {// 表示有现成持有锁,读或写
        if (w == 0 || current != getExclusiveOwnerThread())// w = 0 表示有读线程持有锁,或者持有锁的线程不是当前线程,当前写线程阻塞
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)// 校验是否超过最大值
            throw new Error("Maximum lock count exceeded");
        setState(c + acquires);// 走到这一步说明持有锁的就是当前线程,也即锁重入
        return true;
    }
    if (writerShouldBlock() || // 写锁是否需要阻塞,对于非公平锁来说永远返回false不要阻塞,而对于公平锁来说,需要看竞争队列中是否有等待节点
        !compareAndSetState(c, c + acquires)) // CAS 抢锁
        return false;
    setExclusiveOwnerThread(current); // 走到这一步表明获取锁成功
    return true;
}
tryWriteLock 方法

尝试获取写锁,不会阻塞线程,获取失败立即返回

final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState(); // 保存当前 state 变量的值
    if (c != 0) {
        int w = exclusiveCount(c);// 获取写锁个数
        // w = 0 表示有读线程持有锁,或者持有锁的线程不是当前线程,当前写线程阻塞
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))// CAS 抢锁
        return false;
    setExclusiveOwnerThread(current);// 走到这一步表明获取锁成功
    return true;
}
tryRelease 方法

释放写锁

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())// 校验当前线程是否是持有锁的线程,不允许释放其它线程的锁
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;// 写锁释放锁,只需要直接对 state 进行减法操作即可
    boolean free = exclusiveCount(nextc) == 0; // 等于 0 说明是最后一个写锁释放锁
    if (free)
        setExclusiveOwnerThread(null);// 清空 exclusiveOwnerThread 信息
    setState(nextc);// 更新 state
    return free;
}
tryAcquireShared 方法

获取读锁,由于读锁是共享的,也即可以多个线程同时持有,为了记录读锁的重入次数这里引入了 HoldCounter 和 ThreadLocal,每个线程一个 HoldCounter 对象,用于记录该线程的所重入次数,然后将其保存在 ThreadLocal 中。如果是第一个读锁线程,这里通过 firstReader 和 firstReaderHoldCount 两个变量来进行优化,通过它俩保存线程信息和重入次数。

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();// 保存当前 state 变量的值
    if (exclusiveCount(c) != 0 &&// 有写锁在等待,并且是其它线程正持有写锁
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);// 保存当前读锁个数
    // 读线程是否需要阻塞,竞争队列中有等待节点阻塞(公平锁),等待在队列首部的是互斥线程阻塞(非公平锁)
    if (!readerShouldBlock() && 
        r < MAX_COUNT &&
        compareAndSetState(c, c + SHARED_UNIT)) {// CAS 获取读锁,并对读锁个数加 1
        if (r == 0) {// 如果是第一个获取读锁的线程,使用 firstReader 进行优化处理
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {// 所重入
            firstReaderHoldCount++;
        } else {// 非第一个获取读锁的线程 使用 HoldCounter 记录线程信息以及所重入次数
            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;
    }
    // 走到此步说明读锁需要阻塞或 CAS 抢锁失败,fullTryAcquireShared 是完整版的抢锁流程,上一步是对程序的一种优化行为,判断前置优化
    return fullTryAcquireShared(current); 
}
fullTryAcquireShared 方法

完整版的读锁获取流程,for(;😉 保证成功获取锁

final int fullTryAcquireShared(Thread current) {
    HoldCounter rh = null;
    for (;;) {
        int c = getState();// 保存当前 state 值
        if (exclusiveCount(c) != 0) {// 判断是否有写锁
            // 当前持有写锁的线程不是当前线程,返回 -1,防止死锁发生,此判断表明 读可以升级为写,
            if (getExclusiveOwnerThread() != current)
                return -1;
        } else if (readerShouldBlock()) {// 读线程是否需要阻塞,竞争队列中有等待节点阻塞(公平锁),等待在队列首部的是互斥线程阻塞(非公平锁)
            if (firstReader == current) {
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get(); // 获取当前的计数器
                        if (rh.count == 0)// count 为 0 表示当前线程并未持有锁
                            readHolds.remove(); 
                    }
                }
                if (rh.count == 0)// 当前现成应该阻塞,并且当前线程不在持有锁,返回 -1 由 AQS 负责其阻塞流程操作
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 下面的逻辑 同 tryAcquireShared 方法中的 if 条件判断
        if (compareAndSetState(c, c + SHARED_UNIT)) {// CAS 获取读锁,并对读锁个数加 1
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // 缓存最后一个获取读锁的线程
            }
            return 1;// 告诉 AQS 获取锁成功,让 AQS 唤醒后续等待的读线程
        }
    }
}
tryReadLock 方法

代码实现同 tryAcquireShared 方法,不同在于该方法不阻塞,获取所失败直接返回

final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        int r = sharedCount(c);
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } 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 true;
        }
    }
}
tryReleaseShared 方法

读锁释放流程分析

1、如果是首个获取读锁的线程,直接操作 firstReader 和 firstReaderHoldCount 变量,当 firstReaderHoldCount = 1 时表明重入次数以更新,将 firstReader 置空

2、非首个获取读锁的线程,通过 HoldCounter 操作重入计数信息

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) { // 判定当前线程是否是首个获取读锁的线程
        if (firstReaderHoldCount == 1)// 当等于 1 时,置空 firstReader
            firstReader = null;
        else
            firstReaderHoldCount--;// 重入次数减 1 
    } else {
        HoldCounter rh = cachedHoldCounter;// 保存之前缓存的最后一个获取读锁的 HoldCounter 
        if (rh == null || rh.tid != getThreadId(current))// 当前线程不是最后一个获取读锁的线程
            rh = readHolds.get();// 通过 ThreadLocal 获取当前线程对应的 HoldCounter 信息
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();// 从 ThreadLocal 中删除 当前线程的 HoldCounter
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;// 重入次数减 1 
    }
    // for(;;) 保证读锁释放成功
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

总结:通过上述源码的学习,我们知道了读写锁的实现流程。AQS 提供 state 单变量和 队列的功能,ReentrantReadWriteLock 通过操作这两个信息完成自己的功能。通过将一个整型变量 state 切割为 高低 16 位,分别用于保存读锁和写锁的信息,高 16 位表示读锁,低 16 位表示写锁。因为写锁是互斥的,同一时刻只有一个线程可以获取锁,使用低位直接进行加减操作即可,操作简单;而读锁是共享的,同一时刻可以多个线程持有锁,为了记录每个线程读锁的重入次数,这里引入了 HoldCounter 来记录线程信息和重入次数。为了优化,如果是第一个获取读锁的线程直接通过 firstReader 记录线程信息,firstReaderHoldCount 记录该线程的重入次数,减少对 ThreadLocal 的操作。如果是最后一个获取读锁的线程通过 cachedHoldCounter 缓存 HoldCounter 。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值