Java锁lock源码分析(三)读写锁

Java锁lock源码分析(三)读写锁

摘自网上一段话:
ReadWriteLock管理一组锁,一个是只读的锁,一个是写锁。读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。
所有读写锁的实现必须确保写操作对读操作的内存影响。换句话说,一个获得了读锁的线程必须能看到前一个释放的写锁所更新的内容。
读写锁比互斥锁允许对于共享数据更大程度的并发。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。ReadWriteLock适用于读多写少的并发情况。

前文Java锁Lock源码分析(一)提过在java的Lock中获取锁就表示AQS的volatile int state =1表示获取到了独占锁,state>1表示当前线程重入锁(获取锁了再次获取到了锁)即大于0就表示获取到了独占锁。

独占就意味着排队,失败,系统吞吐量下降,用户体验下降等等。有些情况不要独占,比如说读与读不互斥,读与写互斥(但是写锁可降级为读锁),写与写互斥的这样会提升系统的吞吐量。读写锁就是完成这个功能的主角,先上一段代码坐下证明:

读写请求模拟:
读写锁测试代码1

写锁代码:
写锁

读锁代码:
读锁

我们看下最关键结果:
测试结果

这个结果已经说明问题了,读取的时候没有写入,写入的时候没有其他写入,没有其它读,读的时候有其它读,很直白。

那么问题就来了,之前说过,AQS的成员volatile int state就是表示获取成功了锁,那么读写锁是怎么做到读与读不互斥而共享的,写与写是怎么互斥的。

1)AQS的volatile int state;int类型是32位的高16位表示写锁,低16位表示读锁。一个字段技能表示读锁,也能表示写锁。
2)实现原理:写锁即低16位c & (1<<16 -1) 读锁即高16位 c >>> 16得出的值表示AQS的state

ReentrantReadWriteLock的抽象AQS实现Sync
源码中有几行注释方法和属性就是上面两句话的总结:
读写锁实现

上面的代码已经很清楚的演示了ReentrantReadWriteLock的使用方法。
读锁:
我们先讲写锁的获取(相比于读锁它看起来更加简单)
ReentrantReadWriteLock.WriteLock.lock()

public void lock() {
    sync.acquire(1);
}
//同样的还是进入到了AQS的模板方法
public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
}

主要还是看下ReentrantReadWriteLock是如何重写tryAcquire(1)方法的:

protected final boolean tryAcquire(int acquires) {
     Thread current = Thread.currentThread();
     int c = getState();
     //1)w=c&(1<< 16-1)写锁的数量 大于0一定是同一个线程重入的
     int w = exclusiveCount(c);
     //2)有线程占用过锁
     if (c != 0) {
         //3)被写锁独占,不是当前线程(其他线程获取到的写锁)则失败
         if (w == 0 || current != getExclusiveOwnerThread())
             return false;
         //4)被写锁独占,而且是当前线程重入的次数大于1<<16-1则抛出异常    
         if (w + exclusiveCount(acquires) > MAX_COUNT)
             throw new Error("Maximum lock count exceeded");
         //5)被写锁独占,是当前线程获取到的锁,重入
         setState(c + acquires);
         return true;
     }
     //6)没有线程持有锁
     if (writerShouldBlock() ||
         !compareAndSetState(c, c + acquires))
         return false;
     setExclusiveOwnerThread(current);
     return true;
 }

上面的注释已经是非常清楚,这里只是对第6)做下补充:
既然一下线程也没有获取到锁为什么不直接cas设置抢占锁呢?不要忘记ReentrantReadWriteLock也是支持公平锁和费公平锁的,也支持重入锁

公平锁)FailSync判断AQS的双向链表有没有节点,有的话直接返回失败,没有的话CAS抢占一下 –公平的体现 没有排队才抢占一下,所以writeShouldBlock是判断队列中有无排队的。
非公平锁)NonfairSync直接不管有没有排队的直接抢一下 所以writeShouldBlock什么也不判断直接返回false

PS:6)中失败即公平锁队列中有排队的或者 cas在失败,改线程是如何进入到队列(双向链表)的可以参考之前的两篇文章Java锁lock源码分析(一),这里不做解释了。

ReentrantReadWriteLock.ReadLock.lock()

public void lock() {
     sync.acquireShared(1);
}

还是走的AQS的模板方法:
共享锁实现类
PS:JUC包下的几个重要线程同步工具类如Semaphore、CountDownLatch、ReentrantReadWriteLock正式共享锁的实现,它们都是重写了AQS的tryAcquireShared(args)方法。

我们看下在ReentrantReadWriteLock中的tryAcquireShared方法的具体实现:

protected final int tryAcquireShared(int unused) {
     Thread current = Thread.currentThread();
     int c = getState();
     //1)判断写锁 c & (1<<16-1)!=0,写锁不是当前线程
     if (exclusiveCount(c) != 0 &&
         getExclusiveOwnerThread() != current)
         return -1;
     //2)c >>>16
     int r = sharedCount(c);
     //3)等待队列没有写线程,成功获取到读锁的线程数少于1<<16-1,cas设置成功
     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;
     }
     //cas不成功,超出读锁数量,或则写线程在排队  多次重试
     return fullTryAcquireShared(current);
 } 

补充下:

对于1)判断有无写锁跟读锁互斥这个这正常,后边还有一个判断独占写锁是否是当前线程—写锁是可以降级为读锁的
对于3)那读锁来说AQS的state通过计算sharedCount(state)表示的读锁的总数,但是每个线程能获取多少个锁是没有统计,后期的释放锁就会有问题所以多出来了HoldCounter来表示当前线程获取锁的个数(能重入的)

读写线程是如何进入到等待队列,公平锁与非公平锁的区别,如何重入以前都说过Java锁lock源码分析(一),我们只需要知道读写锁还是操作的AQS的state变量,高位表示写锁,低位表示读锁,

ReentrantReadWriteLock的抽象AQS实现Sync
获取锁的总数量:

static final class HoldCounter {
       int count = 0;
       final long tid = getThreadId(Thread.currentThread());
}
static final class ThreadLocalHoldCounter
            extends ThreadLocal<HoldCounter> {
     public HoldCounter initialValue() {
                return new HoldCounter();
    }
}
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
}

猜你喜欢

转载自blog.csdn.net/mayongzhan_csdn/article/details/80731935