07 JUC 之 ReentrantReadWriteLock

ReadWriteLock

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading
     */
    Lock readLock();

    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing
     */
    Lock writeLock();
}

ReentrantReadWriteLock类是接口ReadWriteLock的具体实现类,支持类似于ReentrantLock的语义。

ReentrantReadWriteLock的介绍翻译:

此类不会对锁访问强加读取器或写入器首选项排序。但是,它确实支持可选的公平策略。

非公平模式:当构造为非公平(默认)时,读写锁的进入顺序是未指定的,受可重入约束。连续争用的非公平锁可以无限期地推迟一个或多个读写器线程,但通常比公平锁具有更高的吞吐量。

公平模式:当构造为公平时,线程使用近似到达顺序策略来竞争进入。当当前持有的锁被释放时,等待时间最长的单个写入线程将被分配写入锁,或者如果有一组读取线程等待的时间超过所有等待的写入线程,则该组将被分配读取锁。

如果写入锁被保持,或者存在等待的写入线程,则试图获取公平读取锁(不可重入)的线程将被阻塞。线程将不会获取读锁,直到最老的当前等待写入线程获取并释放写锁之后。当然,如果等待的写入器放弃等待,让一个或多个读线程成为队列中最长的等待者,并释放写锁,那么这些读线程将被分配读锁。

试图获取公平写锁(不可重入)的线程将阻塞,除非读锁和写锁都是空闲的(这意味着没有等待线程)。(请注意,非阻塞的{ReadLock#tryLock()}和{WriteLock#tryLock()}方法不支持此公平设置,如果可能,将立即获取锁,而不考虑等待的线程。)

可重入:

此锁允许读取器和写入器以{@link ReentrantLock}的样式重新获取读或写锁。在写入线程持有的所有写入锁都被释放之前,不允许使用不可重入读取器。

此外,写入程序可以获取读锁,但反之亦然。在其他应用程序中,当在调用或回调在读锁下执行读取的方法时保持写锁时,可重入性非常有用。如果读取器试图获取写锁,它将永远不会成功。

锁降级:

重新进入还允许通过获取写锁,然后获取读锁,然后释放写锁,从写锁降级为读锁。但是,从读锁升级到写锁是不可能的。

锁获取中断:

读锁和写锁都支持在锁获取期间中断。

Condition的支持

写锁提供了一个{Condition}实现,该实现在写锁方面的行为与{ReentrantLock#newCondition}提供的{Condition}实现对{RentrantLock}的行为相同。当然,这个{@link Condition}只能与写锁一起使用。

读取锁不支持{Condition},{readLock().newCondition()}抛出{UnsupportedOperationException}。

ReentrantReadWriteLocks可用于提高某些类型集合的某些使用的并发性。这通常只在集合预期很大、由更多的读线程访问而不是写线程访问,并且需要开销大于同步开销的操作时才有价值。例如,这里有一个使用TreeMap的类,该类应该很大,并且可以并发访问。

ReentrantReadWriteLock的内部类关系

ReentrantReadWriteLock的简单使用方式

public class JucTest {

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

    public static void main(String[] args) {
        try{
            readLock.lock();

        }finally {
            readLock.unlock();
        }
        readWriteLock.getReadLockCount();
        try{
            writeLock.lock();

        }finally {
            writeLock.unlock();
        }
    }
}

ReentrantReadWriteLock源码流程分析

构造方法

ReentrantReadWriteLock默认也是非公平锁,通过构造方法的参数true或false来选择。true是公平锁,false是非公平锁。

同时,对于读锁和写锁,也会随着参数设置自身为公平锁还是非公平锁。

private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;

public ReentrantReadWriteLock() {
    this(false);
}

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; }

内部类Sync说明

  • 因为Sync类是FairSync和NonfairSync的父类,所以在new 这俩子类时,会先初始化Sync类,执行new Sync()构造方法。
  • Sync类里有两个内部类:HoldCounter、ThreadLocalHoldCounter,在Sync类初始化的时候也完成了初始化,这两类的作用是多个不同线程进行共享锁获取,每个线程在自己拥有的副本里,记录自己的重入数。
  • 提供了sharedCount、exclusiveCount方法返回当前的共享持有数和独占持有数。
  • 提供了readerShouldBlock、writerShouldBlock两个抽象方法供它的子类,FairSync和NonfairSync去实现。
  • 因为继承AQS类,就必须要要实现指定的方法,这些都在Sync类里被实现了。

ReentrantReadWriteLock.Sync#tryAcquire方法执行流程解释

  1. 如果读计数非零或写计数非零且所有者是不同的线程,则失败。
  2. 如果计数饱和,则失败,抛出异常。(这只会在count已经非零的情况下发生。)
  3. 如果writerShouldBlock返回true(在公平锁时会有可能返回true)获取锁失败
  4. 尝试获取锁,更新state值失败,获取锁失败。
  5. 否则,如果是,更新状态并设置所有者。
protected final boolean tryAcquire(int acquires) {
    /*
     * Walkthrough:
     * 1. If read count nonzero or write count nonzero
     *    and owner is a different thread, fail.
     * 2. If count would saturate, fail. (This can only
     *    happen if count is already nonzero.)
     * 3. Otherwise, this thread is eligible for lock if
     *    it is either a reentrant acquire or
     *    queue policy allows it. If so, update state
     *    and set owner.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    int w = exclusiveCount(c);
    if (c != 0) {
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires);
        return true;
    }
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

ReentrantReadWriteLock.Sync#tryRelease方法执行流程解释

  1. 如果不是当前线程持有锁,那么就不该执行该方法,抛出异常。
  2. state值进行相应的减去,并将结果更新。
  3. 如果此时独占式锁都释放了,那么就移除持锁的线程。
protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

ReentrantReadWriteLock.Sync#tryAcquireShared方法执行流程解释

  1. 如果写锁(独占式)被另一个线程持有,则失败。否则,该线程符合锁定wrt状态的条件。
  2. 共享模式下允许多线程读,即readerShouldBlock等于false。公平锁的时候会根据当前线程在队列的位置返回true或false,非公平锁时要判断队列的线程是否存在以及线程是否共享等,返回true或false。
  3. 允许读共享,且尝试获取锁成功。那么就根据当前共享锁的个数情况,选择更新持有个数、第一个获取锁的线程,以及更新在守护线程里的个数和线程信息。
  4. 不允许读共享或者此时获取锁失败了,那么以死循环方式执行尝试获取共享模式锁的过程,成功后更新共享信息,失败后返回-1
protected final int tryAcquireShared(int unused) {
    /*
     * Walkthrough:
     * 1. If write lock held by another thread, fail.
     * 2. Otherwise, this thread is eligible for
     *    lock wrt state, so ask if it should block
     *    because of queue policy. If not, try
     *    to grant by CASing state and updating count.
     *    Note that step does not check for reentrant
     *    acquires, which is postponed to full version
     *    to avoid having to check hold count in
     *    the more typical non-reentrant case.
     * 3. If step 2 fails either because thread
     *    apparently not eligible or CAS fails or count
     *    saturated, chain to version with full retry loop.
     */
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        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 1;
    }
    return fullTryAcquireShared(current);
}

final int fullTryAcquireShared(Thread current) {
    /*
     * This code is in part redundant with that in
     * tryAcquireShared but is simpler overall by not
     * complicating tryAcquireShared with interactions between
     * retries and lazily reading hold counts.
     */
    HoldCounter rh = null;
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
            // else we hold the exclusive lock; blocking here
            // would cause deadlock.
        } else if (readerShouldBlock()) {
            // Make sure we're not acquiring read lock reentrantly
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                if (rh.count == 0)
                    return -1;
            }
        }
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            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; // cache for release
            }
            return 1;
        }
    }
}

ReentrantReadWriteLock.Sync#tryReleaseShared方法执行流程解释

  1. 如果当前线程就是共享锁的第一个线程,那么就对应的进行统计个数减一,或者重置第一个线程
  2. 如果不是第一个线程,那么就从ThreadLocal缓存里获取加锁的线程,并进行个数减一
  3. 死循环方式使用CAS进行state值的修改,如果修改成功且共享锁完全释放,则return true,否则false。
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } 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;
    }
    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;
    }
}

在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。

在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。

一个线程要想同时持有写锁和读锁,必须先获取写锁再获取读锁;写锁可以“降级”为读锁;读锁不能“升级”为写锁。

读锁加锁

因为ReadLock实现了Lock接口,并且组合使用了内部类Sync,在读锁进行加锁执行lock()方法时,实际上还是访问了AQS类的acquireShared(1)方法。

在该方法里先执行内部类Sync实现的tryAcquireShared(1)方法,尝试获取共享模式的锁,如果失败(返回结果小于0),就执行doAcquireShared(1)方法。

读锁释放锁

读锁释放锁实际还是访问了AQS类的releaseShared方法。AQS类根据多态执行的是Sync类里的tryReleaseShared方法。如果共享锁都完全释放了,才执行队列唤醒功能。

写锁加锁

访问的是AQS的acquire方法,然后再执行ReentrantReadWriteLock.Sync类的tryAcquire方法。后续流程与ReentrantLock加锁的执行流程和方式一致

写锁释放锁

访问的是AQS的release方法,然后再执行ReentrantReadWriteLock.Sync类的tryRelease方法。后续流程与ReentrantLock加锁的执行流程和方式一致。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值