ReentrantReadWriteLock

27 篇文章 0 订阅

ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离, 读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。
同步状态的高16位用来表示读锁被获取的次数 ,低16位为写锁状态

1 特点

1.1 优点

1 有公平和非公平2个版本
2 锁降级
3 支持重入
4 支持condition

1.2 缺点

1 饥饿
2 不支持锁升级
在同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。

2 组成

读写锁公用一个 sync对象

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

2.1 主要成员

public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    private static final long serialVersionUID = -6992448646407690164L;
    /** Inner class providing readlock */
    private final ReentrantReadWriteLock.ReadLock readerLock;
    /** Inner class providing writelock */
    private final ReentrantReadWriteLock.WriteLock writerLock;
    /** Performs all synchronization mechanics */
    final Sync sync;

2.2 主要内部类

2.2.1 Sync

abstract static class Sync extends AbstractQueuedSynchronizer {  
      static final int SHARED_SHIFT   = 16;
       // 由于读锁用高位部分,所以读锁个数加1,其实是状态值加 2^16
       static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
       // 写锁的可重入的最大次数、读锁允许的最大数量
       static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
       // 写锁的掩码,用于状态的低16位有效值
       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; }
}

1 tryAcquire主要作用:用于写锁,控制states,是否阻塞,返回false阻塞,需要进入AQS的CLH队列。

允许锁升级,即先获得了读,可以获得写锁。

        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);
            //1 持有锁,有可能自己,也可能是其他,可能是读锁,也可能写锁。
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //1.1 写锁未被持有(读锁已经被持有)或其他线程持有写锁,返回失败
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;
                //1.2 大于最大值,返回异常   
                if (w + exclusiveCount(acquires) > MAX_COUNT)
                    throw new Error("Maximum lock count exceeded");
                // Reentrant acquire
                //1.3 肯定是获的自己写锁,更新states。
                setState(c + acquires);
                return true;
            }
            //2 没有线程持有锁(读锁或写锁)
            //2.1 应该阻塞(与公平规则有关),返回失败
            if (writerShouldBlock() ||
             //2.2 CAS 状态失败 ,返回失败
                !compareAndSetState(c, c + acquires))
                return false;
            setExclusiveOwnerThread(current);
            return true;
        }

2 tryRelease:释放读锁states,返回是否,移除当前节点,需要唤醒CLH队列的下一个节点。

       protected final boolean tryRelease(int releases) {
            //1 当前排他线程不是当前线程抛出异常
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //2 计算release后的 states值    
            int nextc = getState() - releases;
            //3 判断当前线程重入次数为0
            boolean free = exclusiveCount(nextc) == 0;
            //4 如果没有重入了,需要释放锁
            if (free)
                setExclusiveOwnerThread(null);
                
            setState(nextc);
            //4 释放需要释放锁
            return free;
        }

3 tryAcquireShared
允许锁降级,即先获得了写锁,可以获得读锁,。

        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            //1. If write lock held by another thread, fail.
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;
            int r = sharedCount(c);
            //2 不应该被阻塞,读锁次数合法,CAS更新读锁的states+1成功,返回1,不需要阻塞
            if (!readerShouldBlock() &&
                r < MAX_COUNT &&
                compareAndSetState(c, c + SHARED_UNIT)) {
                //2.1 未有线程持有读锁
                if (r == 0) {
                    //2.1.1 未有线程持有读锁,首节点置为当前线程
                    firstReader = current;
                    //2.1.2 未有线程持有读锁,首节点获得次数为1,
                    firstReaderHoldCount = 1;    
                }
                //2.2 持有读锁线程的头节点为自己 
                else if (firstReader == current) {
                    //2.2.2 持有读锁线程的头节点为自己,首节点获得计数次数为1,
                    firstReaderHoldCount++;
                }
                //2.3 其他线程是持有读锁线程的头节点  
                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;
            }
            //3 如果第二步失败,具有完整重试循环的版本
            return fullTryAcquireShared(current);
        }
        //该代码与
              * tryAcquireShared,但总体来说更简单
              *使tryAcquireShared与之间的交互复杂化
              *重试和懒惰读取保持计数。
        //      
        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;
                }
            }
        }

tryReleaseShared

        protected final boolean tryReleaseShared(int unused) {
            Thread current = Thread.currentThread();
            //1 更新firstReader和firstReaderHoldCount计数
            // 更新HoldCounter
            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;
            }
            //2 自旋 读锁states-1
            for (;;) {
                int c = getState();
                int nextc = c - SHARED_UNIT;
                if (compareAndSetState(c, nextc))
                    // 释放读锁不影响读线程,如果读锁和写都被释放,将允许写的线程继续执行。
                    return nextc == 0;
            }
        }

3 公平性的确保

如何保证公平与非公共锁:writerShouldBlock和readerShoudBlock抽象方法
//公平锁永远返回 是否有前节点


static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    final boolean writerShouldBlock() {
        return hasQueuedPredecessors();
    }
    final boolean readerShouldBlock() {
        return hasQueuedPredecessors();
    }
}

//非公平锁 写锁永远返回false,读锁涉及共享,通过检查aqs的CLH队列的头节点是否是排他的。


    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = -8159625535654395037L;
        final boolean writerShouldBlock() {
            return false; // writers can always barge
        }
        final boolean readerShouldBlock() {
            /* As a heuristic to avoid indefinite writer starvation,
             * block if the thread that momentarily appears to be head
             * of queue, if one exists, is a waiting writer.  This is
             * only a probabilistic effect since a new reader will not
             * block if there is a waiting writer behind other enabled
             * readers that have not yet drained from the queue.
             */
            return apparentlyFirstQueuedIsExclusive();
        }
    }
    final boolean apparentlyFirstQueuedIsExclusive() {
        Node h, s;
        return (h = head) != null &&
            (s = h.next)  != null &&
            !s.isShared()         &&
            s.thread != null;
    }

4 锁升降级

4.1 锁升级

不允许锁升级,即先获得了读,不可以获得写锁。//1.1部分说明,读锁只要被占用,返回失败。自己获得读锁,也不行。无法升级。

        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);
            //1 持有锁,有可能自己,也可能是其他,可能是读锁,也可能写锁。
            if (c != 0) {
                // (Note: if c != 0 and w == 0 then shared count != 0)
                //1.1 写锁未被持有(读锁已经被持有)或其他线程持有写锁,返回失败  
                if (w == 0 || current != getExclusiveOwnerThread())
                    return false;

4.2 锁升级

允许锁升级,即先获得了写锁,可以获得读锁。//1. 中,只有另一个线程获得写锁,直接返回失败。自己获得写锁没有影响。

        protected final int tryAcquireShared(int unused) {
            Thread current = Thread.currentThread();
            int c = getState();
            //1. If write lock held by another thread, fail.
            if (exclusiveCount(c) != 0 &&
                getExclusiveOwnerThread() != current)
                return -1;

5 读写锁的饥饿问题

ReentrantReadWriteLock非公平模式下,想要获取写锁就变得比较困难了,因为读锁是不互斥的,这个时候大量的读操来读取数据,这个时候就会造成那一条申请写锁的线程会一直被阻塞,这就造成了写线程的饥饿,或者插入其他线程写锁。而无法获得写锁。

6 StampedLock如何解决的饥饿问题

https://blog.csdn.net/chenyixin121738/article/details/109590271

参考

1 https://blog.csdn.net/weixin_43705457/article/details/106211209

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值