ReentrantReadWriteLock

JUC锁: ReentrantReadWriteLock详解

ReentrantReadWriteLock源码分析

类的继承关系
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable

可以看到,ReentrantReadWriteLock实现了ReadWriteLock接口,ReadWriteLock接口只是定义了读锁和写锁的方法体,具体实现需要到子类中去定义。同时还实现了序列化接口。

类的内部类

img

ReentrantReadWriteLock源码中,共定义了5个内部类,在Sync、NonfairSync、FairSync的基础上。还定义了ReadLock和WriteLock两个内部类。

好,我们逐步分析内部类。

Sync内部类
abstract static class Sync extends AbstractQueuedSynchronizer

Sync继承AQS。

在内部类Sync中,还包括两个内部类。

static final class HoldCounter {
    int count = 0;
    // Use id, not reference, to avoid garbage retention
    final long tid = getThreadId(Thread.currentThread());
}

首先先分析HoldCounter类

HolderCounter主要与读锁配套使用,其中包含两个属性

  • count :用于记录读线程重入的次数
  • tid:用于唯一标识一个线程。

接下来分析ThreadLocalHoldCounter

static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程和对象相关联在没有set的情况下,get到的均是initialValue方法中生成的那个HolderCounter对象。

Sync类的属性
版本号
private static final long serialVersionUID = 6317671515068378041L;16位为读锁、低16位为写锁
static final int SHARED_SHIFT   = 16;
读锁单位
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
读锁的最大数量
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
写锁的最大数量
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
本地线程计数器
private transient ThreadLocalHoldCounter readHolds;
缓存的计数器
private transient HoldCounter cachedHoldCounter;
第一个读线程
private transient Thread firstReader = null;
第一个读线程的计数
private transient int firstReaderHoldCount;
Sync类的构造方法
Sync() {
    readHolds = new ThreadLocalHoldCounter();
    setState(getState()); // ensures visibility of readHolds
        }

在构造函数中设置本地线程计数器和AQS的State。

Sync核心函数分析
static int sharedCount(int c)    { return c >>> SHARED_SHIFT; }

此函数可以获得读锁的线程数量,因为在ReentrantReadWriteLock中,高16位表示读锁、低16位表示写锁。所以只需要右移16位,那么就可以获得读锁的线程数量。

static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }

表示写锁的线程数量。

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

此函数用于释放写锁资源,首先会判断该线程是不是独占线程,如果不是独占线程则抛出异常。如果是独占线程,此时写锁资源可以释放,计算释放后的写锁的数量,如果为0,说明成功释放。当前资源不被占用,否则继续占用。

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); //获取写线程的数量
    if (c != 0) { //状态不为0
        // (Note: if c != 0 and w == 0 then shared count != 0)
        if (w == 0 || current != getExclusiveOwnerThread()) //写线程的数量为0,或者当前线程没有独自占用资源
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT) //如果写线程占用的线程数超过了规定的最大线程数
            throw new Error("Maximum lock count exceeded");
        // Reentrant acquire
        setState(c + acquires); //设置AQS 状态
        return true;
    }
    
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires)) //写线程是否应该被阻塞
        return false;
    //设置独占线程
    setExclusiveOwnerThread(current);
    return true;
}

这个函数的作用是获取写锁,那么首先会获取当前线程和状态值,如果状态值为0,说明此时共享资源上既没有写锁也没有读锁。此时需要判断当前写锁是否需要阻塞,如果是非公平的情况下,则不需要阻塞,直接竞争上岗,如果公平方式,则需要检查当前线程节点在CLH队列中是否有前驱结点,也就是等待时间更长的线程节点,如果存在则阻塞当前线程。

如果状态值不为0,则表示此时存在读锁或者写锁在占用资源。如果写线程数量为0或者当前线程没有独占资源,则返回false。如果写线程的数量超过了最大线程数的规定,也返回false。

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread(); //获取当前线程
    if (firstReader == current) { //当前线程是第一个读线程
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1) //读线程占用的资源数为1
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        //当前线程不是第一个读线程
        HoldCounter rh = cachedHoldCounter;
        //计数器为空或者tid并不是当前运行线程的pid
        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;
    }
}

此函数用于读锁释放锁。具体有些没看懂 ,不做总结了。

protected final int tryAcquireShared(int unused) {
            /*
             * Walkthrough:
             * 1. If write lock held by another thread, fail.
             * 2. Otherwise, this thread is eligible for
             *    lock wrt state, so ask if it should block
             *    because of queue policy. If not, try
             *    to grant by CASing state and updating count.
             *    Note that step does not check for reentrant
             *    acquires, which is postponed to full version
             *    to avoid having to check hold count in
             *    the more typical non-reentrant case.
             * 3. If step 2 fails either because thread
             *    apparently not eligible or CAS fails or count
             *    saturated, chain to version with full retry loop.
             */
    		//获取当前线程
            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成功
                if (r == 0) {
                    //如果读锁数量为0 则设置当前线程为第一个读线程
                    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);
        }

以上是Sync主要的内部方法,主要定义了读锁的获取与释放、写锁的获取与释放。

其他内部类的操作实际上就是对Sync进行操作,二次封装而已。

ReentrantReadWriteLock的属性
public class ReentrantReadWriteLock
        implements ReadWriteLock, java.io.Serializable {
    // 版本序列号    
    private static final long serialVersionUID = -6992448646407690164L;    
    // 读锁
    private final ReentrantReadWriteLock.ReadLock readerLock;
    // 写锁
    private final ReentrantReadWriteLock.WriteLock writerLock;
    // 同步队列
    final Sync sync;
    
    private static final sun.misc.Unsafe UNSAFE;
    // 线程ID的偏移地址
    private static final long TID_OFFSET;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            // 获取线程的tid字段的内存地址
            TID_OFFSET = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("tid"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }
}

ReentrantReadWriteLock中包含了读锁、写锁、还有Sync同步队列。

ReentrantReadWriteLock的构造函数
public ReentrantReadWriteLock() {
    this(false);
}
public ReentrantReadWriteLock(boolean fair) {
    // 公平策略或者是非公平策略
    sync = fair ? new FairSync() : new NonfairSync();
    // 读锁
    readerLock = new ReadLock(this);
    // 写锁
    writerLock = new WriteLock(this);
}
  

默认也是非公平的策略,并且还在构造函数中生成了ReadLock、WriteLock对象。

对于ReentrantReadWriteLock的核心函数,其内部都是调用的Sync的函数,这一点跟ReentrantLock一样。

锁降级

锁降级指的是写锁降为读锁的过程。如果写锁释放,然后获取读锁,这种得分段释放获取的方式不能成为锁降级。锁降级:把持住当前的写锁,尝试获取读锁,然后释放写锁。

作用:

锁降级的作用是:如果当前线程获取了写锁,然后将写锁释放,而此刻另一个线程获取了写锁直接修改了数据,那么当前线程是无法感知到数据的更新的。但是如果写锁不释放直接获取读锁,也就是进行锁降级,那么这是当前线程因为拥有了读锁,会阻塞其他不安全的写锁线程,那么当前线程就安全的获取了数据。

总结

  • 读写锁并不完全是排他锁,因为对于读写锁而言,在同一时刻可以允许多个读线程同时访问,但是当写线程访问得时候,所有的读线程和其他的写线程都必须阻塞。
  • 读写锁使用同步队列来维护读写各自的状态,也就是高16位表示读状态,低十六位表示写状态。
  • 写锁是支持重新进入的排他锁,如果当前线程已经获取了写锁,重新进入的时候,写状态加1。否则如果当前线程在获取写锁的时候,读锁已经获取了也就是说读锁状态值不为0,或者有线程获取了写锁,但是这个线程并不是当前线程,此时当前线程的操作被阻塞。
  • 读锁是支持重新进入的共享锁,如果当前线程已经获得读锁,重新进入的时候,读锁状态值加1。如果读锁在重新进入的时候,发现已经有写锁在访问了,此时当前线程的读锁操作将被阻塞。
    不完全是排他锁,因为对于读写锁而言,在同一时刻可以允许多个读线程同时访问,但是当写线程访问得时候,所有的读线程和其他的写线程都必须阻塞。
  • 读写锁使用同步队列来维护读写各自的状态,也就是高16位表示读状态,低十六位表示写状态。
  • 写锁是支持重新进入的排他锁,如果当前线程已经获取了写锁,重新进入的时候,写状态加1。否则如果当前线程在获取写锁的时候,读锁已经获取了也就是说读锁状态值不为0,或者有线程获取了写锁,但是这个线程并不是当前线程,此时当前线程的操作被阻塞。
  • 读锁是支持重新进入的共享锁,如果当前线程已经获得读锁,重新进入的时候,读锁状态值加1。如果读锁在重新进入的时候,发现已经有写锁在访问了,此时当前线程的读锁操作将被阻塞。
  • 读锁可重入读锁,读锁之后不能重入写锁。写锁可重入写锁和读锁。写锁之后重入读锁是锁降级。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值