Lock(ReentrantReadWriteLock)内部读写锁的问题

ReentrantReadWriteLock维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。

关于共享变量

在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着两个一对锁,需要用一个变量维护多种状态。所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16为表示读,低16为表示写。分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过为运算。假如当前同步状态为S,那么写状态等于 S & 0x0000FFFF(将高16位全部抹去),读状态等于S >>> 16(无符号补0右移16位)。

 

写锁的获取

和reentrantlock锁获取的状态基本相同,主要多了一层有没有读锁的存在

 protected final boolean tryAcquire(int acquires) {
        Thread current = Thread.currentThread();
        //当前锁个数
        int c = getState();
        //写锁
        int w = exclusiveCount(c);
        if (c != 0) {//有没有读锁的存在
            //c != 0 && w == 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;
        }
        //是否需要阻塞
        if (writerShouldBlock() ||
                !compareAndSetState(c, c + acquires))
            return false;
        //设置获取锁的线程为当前线程
        setExclusiveOwnerThread(current);
        return true;
    }

读锁的获取,麻烦很多

读锁的获取只要没有写锁占用并且不是当前线程(可能存在写锁降级的情况所以还需要后面一个判断,降级了我自己是可以去读的啊),而且读锁没有超过最大获取数量都可以尝试获取读锁。因为读锁是有有多个的,每个读锁也是可重入的,也就是大于0,我们需要专门设置一个变量去记录

protected final int tryAcquireShared(int unused) {

    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current) //写锁不等于0的情况下,验证是否是当前写锁尝试获取读锁
        return -1;
    int r = sharedCount(c);  //获取读锁数量
    if (!readerShouldBlock() && //读锁不需要阻塞
        r < MAX_COUNT &&  //读锁小于最大读锁数量
        compareAndSetState(c, c + SHARED_UNIT)) { //CAS操作尝试设置获取读锁 也就是高位加1
        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);
}

尝试用ReentrantReadWriteLock实现一个简单的缓存?

自己实现一个读写锁?

读写锁会发生写饥饿的情况吗?如果发生,有没有比较好的解决办法?

ReentrantReadWriteLock会发生饥饿的情况,具体JDK8中新增的改进读写锁---StampedLock改进

具体参考:(下面两个讲的非常好)

https://juejin.im/post/5b9df6015188255c8f06923a

http://cmsblogs.com/?p=2213

posted @ 2019-05-21 21:20  LeeJuly  阅读(279)  评论(0)    收藏  举报