从同步器看AQS(2)-ReadWriteLock

1. 简介

ReadWriteLock是中包括两种锁,一种独占的写锁,以及一种共享的读锁。同时,这两种锁都能提供公平与非公平两种模式,在初始化ReadWriteLock时设置。

如果看过上一篇从同步器看AQS-ReentrantLock的话,肯定已经对独占的写锁有所了解了。

2. 写锁

写锁的操作与ReentrantLock极其相似,同样的,他有一个内部类Sync继承了AQS,同时重写了他的tryAcquire和tryRelease方法。 如果了解ReentrantLock的话,看起写锁的实现来会非常轻车熟路。

2.1 写锁lock操作

同ReentrantLock一样,写锁的lock操作往下调用AQS的acquire方法。然后会先调用tryAcquire(Sync重写)方法,那么我们来看一下tryAcquire的具体实现吧。

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    int c = getState();
    //获取独占(写锁)获取的次数
    int w = exclusiveCount(c);
    if (c != 0) {
        //如果写锁的数量为0或者占有锁的不是当前线程
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        setState(c + acquires);
        return true;
    }
    //writerShouldBlock()是为了实现公平与非公平的情况的
    //目的是为了让新来的线程直接入队或者直接进行尝试
    if (writerShouldBlock() ||
            !compareAndSetState(c, c + acquires))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}

看过ReentrantLock的朋友肯定已经发现这个方法其实和ReentrantLock的极为相似,都提供了独占锁的特性且都通过类似的策略来实现公平与非公平特性。

相信大家都知道读写锁,在先获取读锁的情况下,再获取写锁会发生死锁的情况,那到底是为什么呢?其实就是因为if (w == 0 || current != getExclusiveOwnerThread())这个判断导致的,进入这个判断之前的判断是状态不等于0,也就是说这个时候有线程获得了锁。假设这个时候获取到的是读锁,那么w==0成立,于是就返回false,让线程进入了队列等待。这样的话就形成了死锁。

2.2 写锁unlock操作

写锁的释放同样调用AQS的release方法,然后其中会调用tryRelease方法,也就是ReadWriteLock的Sync重写的tryRelease。

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

同ReentrantLock一样,写锁的释放也是通过状态值减1来控制可重入的。同时当写锁的重入数为0的时候释放锁

3. 读锁

读锁是一种共享锁,同样提供了公平与非公平两种形式,读写锁的内部Sync对象实现了tryAcquireShared。读锁就是调用了这个方法去获取锁的。

3.1 读锁lock操作

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);
    //readerShouldBlock同样是为了实现公平与非公平特性用的
    if (!readerShouldBlock() &&
            r < MAX_COUNT &&
            compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            //记录第一个获取读锁的线,同时重入为1
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            //重入数量+1
            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);
}

可以看出读锁能够多个线程同时获取。同时我们发现 if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)这个判断,如果当一个线程已经获取写锁,那么他同样也能获得读锁。

3.1 读锁unlock操作

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

理解写锁释放的情况下,理解读锁的释放应该非常容易。

因为读锁是共享的,所以他们分别都会记录一个自己的重入数,同时会在state上记录总的重入数。那么释放的操作就是在这些值上做减1操作。

4. 小结

对于读写锁,单一线程获取锁的顺序如果是读锁写锁是会死锁,而写锁读锁的顺序是允许的。为什么这样子呢,我认为和读写锁的性质决定的。因为读锁是共享的,如果这个时候允许获取写锁的,那么会变成当前线程有读锁和写锁,其他线程也有读锁的情况,这就破坏了性质。而写锁获取以后,其他线程无法获得锁,那么当前线程再去获取读锁也是不会破坏性质的。

如果对于读写锁还有疑问的盆友,其实可以亲自去追一下源码,因为源码里有超级详细的一个流程注解,标注了step1,step2,step3。


感谢您的阅读, 如果有什么地方写的有问题的,请麻烦指正一下,谢谢╰(°▽°)╯

转载于:https://my.oschina.net/u/3173942/blog/1580608

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值