简介

ReentrantReadWriteLock 从字面意思可以看出,是和重入、读写有关系的锁,实际上 ReentrantReadWriteLock 确实也是支持可重入的读写锁,并且支持公平和非公平获取锁两种模式。

为什么会出现读写锁?

普通锁可以保证共享数据在同一时刻只被一个线程访问,就算有多个线程都只是读取的操作,也还是要排队等待获取锁,我们知道数据如果只涉及到读操作,是不会出现线程安全方面的问题的,那这部分加锁是不是可以去掉?或者是加锁不互斥?如果在读多写少的情况下,使用普通的锁,在所有读的情况加锁互斥等待会是一个及其影响系统并发量的问题,如果所有的读操作不互斥,只有涉及到写的时候才互斥,这样会不会大大的提高并发量呢?答案是肯定的,ReentrantReadWriteLock 就是这样干的,读读不互斥,读写、写读、写写都是互斥的,可以大大提高系统并发量。

源码分析

类结构

ReentrantReadWriteLock 仅实现了ReadWriteLock接口

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {...}

ReadWriteLock 接口仅有两个方法,分别是 readLock()writeLock();

主要属性

ReentrantReadWriteLock 有3个重要的属性,分别是读锁readerLock,写锁writerLock和同步器sync,源码如下:

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

主要内部类

  1. Sync:同步器,继承至AbstractQueuedSynchronizer,定义了两个抽象方法,用于两种模式下自定义实现判断是否要阻塞

    abstract static class Sync extends AbstractQueuedSynchronizer{
    	...
            abstract boolean readerShouldBlock();
            abstract boolean writerShouldBlock();
    	...
    }
    
  2. NonfairSync:非公平同步器,用于实现非公平锁,继承Sync

    static final class NonfairSync extends Sync {...}
    
  3. FairSync:公平同步器,用于实现公平锁,继承Sync

    static final class FairSync extends Sync {...}
    
  4. ReadLock:读锁,实现了Lock接口,持有同步器Sync的具体实例

    public static class ReadLock implements Lock, java.io.Serializable {
     ...
          private final Sync sync;
     ...
    }
    
  5. WriteLock:写锁,实现了Lock接口,持有同步器Sync的具体实例

    public static class WriteLock implements Lock, java.io.Serializable {
     ...
          private final Sync sync;
     ...
    }
    

构造方法

有两个默认的构造方法,无参默认采用非公平锁,有参传入true使用公平锁

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

获取读锁:readLock.lock()

读锁主要是按照共享模式来获取锁的,在前面讲AQS的例子中——基于AQS实现自己的共享锁,也是差不多的流程,只不过不同的锁的实现方法tryAcquireShared有一定的区别。ReentrantReadWriteLock 读锁获取过程源码如下:

public void lock() {
    // 共享模式获取锁
    sync.acquireShared(1);
}
// acquireShared 是AQS框架里面的代码
public final void acquireShared(int arg) {
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}
// tryAcquireShared 是RRWLock.Sync 里面的自己实现,所以这里没有公平和非公平所谓之称
protected final int tryAcquireShared(int unused) {
    // 当前想要获得锁的线程
    Thread current = Thread.currentThread();
    // 获取state值
    int c = getState();
    // 独占锁被占用了,并且不是当前线程占有的,返回-1,出去要排队
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    // 读锁共享锁的次数
    int r = sharedCount(c);
    // 判断读是否要阻塞,读共享锁的次数是否超过最大值,CAS 更新锁state值
    // readerShouldBlock 的返回要根据同步器是否公平的具体实现来决定
    if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            // r==0, 设置第一次获得读锁的读者
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            // 持有第一个读者读锁的线程重入计数
            firstReaderHoldCount++;
        } else {
            // 除第一个线程之后的其他线程获得读锁
            // 每个线程每次获得读锁重入计数+1
            // readHolds 就是一个ThreadLocal,里面放的HoldCounter,用来统计每个线程的重入次数 
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        // 获得读锁,返回1
        return 1;
    }
		// 上面if分支没进去时,走这里尝试获取读锁
    return fullTryAcquireShared(current);
}

上面代码中的readerShouldBlock()方法有两种情况下会返回true:

  1. 公平模式下,调用的AQS.hasQueuedPredecessors()方法

    static final class FairSync extends Sync {
        final boolean writerShouldBlock() {
            return hasQueuedPredecessors();
        }
        final boolean readerShouldBlock() {
            return hasQueuedPredecessors();
        }
    }
    
    public final boolean hasQueuedPredecessors() {
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        // head 头结点是当前持有锁的节点,它的下一个节点不是当前线程,返回true,表示应该要阻塞当前线程
        return h != t && ((s = h.next) == null || s.thread != Thread.currentThread());
    }
    

版权声明:本文为admol原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/admol/p/13999889.html