ReentrantReadWriteLock源码分析(JDK1.8)

1、简介

1.1、场景

对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁

1.2、进入锁的规则

线程进入读锁的前提条件:

  • 没有其他线程的写锁
  • 没有写请求或者有写请求,但调用线程和持有锁的线程是同一个

线程进入写锁的前提条件:

  • 没有其他线程的读锁
  • 没有其他线程的写锁

1.3、读写锁的三个重要特性

  1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
  2. 重进入:读锁和写锁都支持线程重进入
  3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁

1.4、读写锁同步状态设计

(1)获取写状态:

    S&0x0000FFFF:将高16位全部抹去

(2)获取读状态:

    S>>>16:无符号补0,右移16位

(3)写状态加1:

     S+1

(4)读状态加1:

  S+(1<<16)即S + 0x00010000

在代码层的判断中,如果S不等于0,当写状态(S&0x0000FFFF),而读状态(S>>>16)大于0,则表示该读写锁的读锁已被获取。

 

2、图解

2.1、继承关系图

2、重要方法调用图

从图中可见读写锁的加锁解锁操作最终都是调用ReentrantReadWriteLock类的内部类Sync提供的方法。Sync对象通过继承AbstractQueuedSynchronizer进行实现,故后续分析主要基于Sync类进行。

 

3、ReadWriteLock接口介绍

内里面有2个方法的定义:readLock()  和  writeLock()

package java.util.concurrent.locks;

public interface ReadWriteLock {
    
    //返回一个读锁
    Lock readLock();

    //返回一个写锁
    Lock writeLock();
}

4、ReentrantReadWriteLock的成员变量与构造方法

//序列码
private static final long serialVersionUID = -6992448646407690164L;

//读锁
private final ReentrantReadWriteLock.ReadLock readerLock;

//写锁
private final ReentrantReadWriteLock.WriteLock writerLock;

//自定义同步器引用(注意,Sync只是引用,具体实现在构造方法里面为 FairSync  NonFairSync)
final Sync sync;

//默认是 非公平
public ReentrantReadWriteLock() {
    this(false);
}

//带参构造:true公平   false非公平
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

5、ReentrantReadWriteLock的5个内部类

分别是 Sync   +   FairSync  +  NonFairSync  +  ReadLock  +  ReadLock

5.1、Sync抽象类介绍

Sync为最重要的组成部分。其中包含了2个内部类 HoldCounter  +  ThreadLocalHoldCounter  。它们之和读锁有关。

FairSync 和 NonFairSync 种都只是简单的实现了抽象方法 writerShouldBlock()   readerShouldBlock() 。仅仅如此!!!

5.1.1、Sync成员变量 及 构造方法

Sync继承于AbstractQueuedSynchronizer,其中主要功能均在AbstractQueuedSynchronizer中完成。

abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 6317671515068378041L;  //版本序列号
    
    //高16为读状态,低16位为写状态(读写锁)。SHARED_SHIFT为移动单元,方面其他计算
    static final int SHARED_SHIFT   = 16;  
    static final int SHARED_UNIT    = (1 << SHARED_SHIFT);  //0x0000FFFF
    static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1; //读锁最大数量
    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; }
    
    //注意以下4个变量之和读锁有关
    //最后一个获取读锁的线程的计数器
    private transient HoldCounter cachedHoldCounter;
    //本地线程计数器(记录当前线程读锁获取次数)
    private transient ThreadLocalHoldCounter readHolds;
    //用来记录第一个获取到读锁的线程
    private transient Thread firstReader = null;
    //用来记录第一个获取到读锁的线程获取读锁的可重入次数
    private transient int firstReaderHoldCount;

    static final class HoldCounter {
        int count = 0;  //读锁重入的次数
        //获取当前线程的ID,唯一标识一个线程
        final long tid = getThreadId(Thread.currentThread());
    } 

    static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
        /**
         * ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,
         *ThreadLocal类可以将线程与对象相关联。
         *在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象。
         */
        public HoldCounter initialValue() {
            return new HoldCounter();
        }
    }   
    
    Sync() {  //自定义同步器构造方法
        readHolds = new ThreadLocalHoldCounter();  //初始化本地线程计数器
        setState(getState()); // AQS设置同步状态
    }                                                                 

5.1.2、重要方法

tryAcquire(int acquires)   尝试获取写锁(独占)

protected final boolean tryAcquire(int acquires) {
            /*
             * 1. 如果读锁不为0  |  写锁不为0且当前线程不是占有锁的线程
             * 2. 如果次数超过最大值,则失败(只有count不为0才有这种情况)
             * 3. 否则,如果此线程是可重入获取线程或队列策略允许的线程,则它有资格被锁定。如果是,请更新状态并设置所有者。
             */
    Thread current = Thread.currentThread();
    int c = getState();   //获取同步状态(32位)
    int w = exclusiveCount(c);   //得到写锁的状态(低16位)
    if (c != 0) {   //如果当前有线程占有锁
        // 存在读锁  或者  当前线程不是已经获取写锁的线程  (情况1)
        if (w == 0 || current != getExclusiveOwnerThread())
            return false; 
        //执行到这里,只可能是 存在写锁,并且当前线程是获取写锁的线程  
        if (w + exclusiveCount(acquires) > MAX_COUNT)  //写锁获取次数超过允许的最大值(情况2)
            throw new Error("Maximum lock count exceeded");
        //情况3
        //执行到这里,已经可以正常获取锁(可重入),注意写锁不是共享锁,这里不是有竞态条件
        setState(c + acquires);   //设置同步状态(低16位直接加)
        return true;
    }
    //  c==0 当前还没有线程获取锁(有竞态条件,用CAS设置status)
    if (writerShouldBlock() ||    //写锁是否应该被阻塞i,具体实现在 FairSync 和 NonFairSync中(这个实现和公平性有关)
        !compareAndSetState(c, c + acquires))  //不应该阻塞,则CAS设置同步状态(可能会失败)
        return false;
    setExclusiveOwnerThread(current);  //设置当前线程占有锁
    //AQS中的exclusiveOwnerThread = thread;
    return true;
}

tryRelease(int releases)   尝试释放写锁(独占锁)

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);  //设置status
    return free;  //返回锁释放标志
}

 


tryAcquireShared(int unused)   尝试获取读锁(共享锁)

protected final int tryAcquireShared(int unused) {
    /*
     * 1. 如果另一个线程持有写锁,则失败
     * 2. 读锁不被阻塞,读锁获取次数小于最大值,CAS设置同步状态成功
     * 3. 步骤2失败,线程不合格(公平锁有前驱) 或者 CAS失败 或者 计数饱和
     */
    Thread current = Thread.currentThread();
    int c = getState();
    // 情况1:写锁不为0,且不是当前线程占有(锁降级在此体现)
    if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
        return -1;
    int r = sharedCount(c);  //读锁状态
    //情况2
    if (!readerShouldBlock() &&   //读锁不应该被阻塞(具体实现区分公平性)
        r < MAX_COUNT &&      //读锁获取次数小于最大值
        compareAndSetState(c, c + SHARED_UNIT)) {  //CAS设置同步状态
        //执行到这里,就可以获取读锁了
        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);  //将当前线程计数器添加到readHolds中
            rh.count++;  //当前线程获取读锁次数+1
        }
        return 1;
    }
    //  情况3:情况2下线程不合格 或者 CAS失败 或者 计数饱和,则进入循环重试
    return fullTryAcquireShared(current);
}
// CAS循环获取读锁,处理CAS未命中,计数饱和,线程不合格
final int fullTryAcquireShared(Thread current) {
    /**此代码与tryAcquireShared中的代码部分冗余,但总体上更简单,
     *因为不会使tryAcquireShared与重试和延迟读取保持计数之间的交互复杂化。
     */
    HoldCounter rh = null; //缓存计数器或者当前线程计数器
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0) {   //写锁不为0
            if (getExclusiveOwnerThread() != current)  //不是当前线程获取写锁
                return -1;
        // 否则我们持有独占锁;在这里阻塞会导致死锁
        } else if (readerShouldBlock()) {  //读线程是否应该被阻塞
            // 确保我们没有以可重入方式获取读锁
            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)) {   
            //CAS设置成功
            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; // 更新缓存
            }
            return 1;
        }
    }
}

 回顾上面说的读锁的获取,都会包含3种情况的判断:

  1. 另一个线程持有写锁,失败(如果持有写锁的是本线程,可以获取,锁降级)
  2. 读锁不被阻塞,获取次数小于最大值,CAS设置
  3. 若上步失败,则进入循环CAS设置

获取读锁,修改 本地线程计数器有3种可能:

  1. 当前没有线程获取读锁,则设置  firstReader = current;  firstReaderHoldCount = 1;
  2. firstReader == current
  3. 否则,先从 循环计数器cachedHoldCounter找,如果不是从  readHolds 种找

tryReleaseShared(int unused)   尝试释放读锁(共享锁)

//如果当前线程已经获取了读锁,则一定会释放成功(循环CAS)。返回同步状态==0
protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {  //如果当前线程是第一个获取到读锁的线程(效率高)
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {  //当前线程不是第一个获取读锁的线程
        HoldCounter rh = cachedHoldCounter;   //获取缓存计数器(最后获取读锁的线程)
        //计数器为空  或者  计数器的tid不为当前正在运行的线程的tid
        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;  //当前获取读锁的次数-1
    }
    for (;;) {   //循环CAS设置状态
        int c = getState();
        int nextc = c - SHARED_UNIT;   //c-0x00010000,读状态-1
        if (compareAndSetState(c, nextc))  //CAS设置同步状态
            // 释放读锁,不影响其他线程的读锁
            // 但如果读锁和写锁都空闲,会影响同步队列(在AQS中实现的)
            return nextc == 0;
    }
}

这里为什么要搞一个firstRead、firstReaderHoldCount呢?而不是直接使用else那段代码?这是为了一个效率问题,firstReader是不会放入到readHolds中的,如果读锁仅有一个的情况下就会避免查找readHolds。

HoldCounter应该就是绑定线程上的一个计数器,而ThradLocalHoldCounter则是线程绑定的ThreadLocal。从上面我们可以看到ThreadLocal将HoldCounter绑定到当前线程上,同时HoldCounter也持有线程Id,这样在释放锁的时候才能知道ReadWriteLock里面缓存的上一个读取线程(cachedHoldCounter)是否是当前线程。这样做的好处是可以减少ThreadLocal.get()的次数,因为这也是一个耗时操作。需要说明的是这样HoldCounter绑定线程id而不绑定线程对象的原因是避免HoldCounter和ThreadLocal互相绑定而GC难以释放它们 (尽管GC能够智能的发现这种引用而回收它们,但是这需要一定的代价),所以其实这样做只是为了帮助GC快速回收对象而已。


 tryWriteLock()   实际上与tryAcquire相同,只是缺少对writersouldblock的调用

final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    if (c != 0) {
        int w = exclusiveCount(c);
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current);
    return true;
}
tryReadLock() 实际上与tryAcquireShared相同,只是缺少对readersouldblock的调用
final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) {
        int c = getState();
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        int r = sharedCount(c);
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        if (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 true;
        }
    }
}

​​​​​​​5.1.3、Sync中的抽象方法(在FairSync 或者 NonFairSync 中实现)

abstract boolean readerShouldBlock();

abstract boolean writerShouldBlock();

5.1.4、其他感兴趣的方法

// 返回所有线程获取读锁的次数
final int getReadLockCount() {
    return sharedCount(getState());
}

// 返回当前线程获取读锁的次数
final int getReadHoldCount() {
    if (getReadLockCount() == 0)
        return 0;

    Thread current = Thread.currentThread();
    if (firstReader == current)
        return firstReaderHoldCount;

    HoldCounter rh = cachedHoldCounter;
    if (rh != null && rh.tid == getThreadId(current))
        return rh.count;

    int count = readHolds.get().count;
    if (count == 0) readHolds.remove();
    return count;
}

 

5.2、FairSync源码分析

FairSync继承Sync,在其基础上重写了2个方法

static final class FairSync extends Sync {
    private static final long serialVersionUID = -2274990926593161451L;
    
    // 两个方法都是立刻返回 boolean,判断方式是看有没有前驱节点
    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;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

5.3、NonFairSync 源码分析

和 FairSync 一样,仅仅实现了 Sync 种的两个抽象方法。

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -8159625535654395037L;

    // 写锁为独占锁,非公平,不用等待
    final boolean writerShouldBlock() {
        return false; 
    }
    // 读锁为共享锁,非公平
    final boolean readerShouldBlock() {
        return apparentlyFirstQueuedIsExclusive();
    }
}
final boolean apparentlyFirstQueuedIsExclusive() {
    Node h, s;
    return (h = head) != null &&
        (s = h.next)  != null &&
        !s.isShared()         &&
        s.thread != null;
}

5.4、ReadLock 源码分析

public static class ReadLock implements Lock, java.io.Serializable {
    
    private static final long serialVersionUID = -5992448646407690164L;
    private final Sync sync;

    protected ReadLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

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

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    public boolean tryLock() {
        return sync.tryReadLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.releaseShared(1);
    }
    
    ......
}

ReadLock里面有一个 Sync 自定义同步器。其实例是 ReentrantReadWriteLock 里面的 Sync 内部类,因此ReadLock里面的方法都是调用Sync里面的共享锁的方法。 

5.5、WriteLock 源码分析

public static class WriteLock implements Lock, java.io.Serializable {
    
    private static final long serialVersionUID = -4992448646407690164L;
    private final Sync sync;

    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

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

    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    public void unlock() {
        sync.release(1);
    }

    ......
}

和 ReadLock 一样,其方法实现都是调用 Sync 类的独占锁的方法

 

6、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;   // 自定义同步器

    public ReentrantReadWriteLock() {
        this(false);  //调用下面那个构造方法
    }

    // 重要!!!
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        // 实际上就是把 ReentrantReadWriteLock的Sync赋给ReadLock的Sync了
        readerLock = new ReadLock(this);   
        writerLock = new WriteLock(this);
    }

    // 两个必要的方法
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    private static final sun.misc.Unsafe UNSAFE;
    private static final long TID_OFFSET;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            TID_OFFSET = UNSAFE.objectFieldOffset   //反射方式获取
                (tk.getDeclaredField("tid"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

ReentrantReadWriteLock 的成员变量非常简单,除了版本序列号、读锁ReadLock、写锁WriteLock、自定义同步器Sync(UNSAFE和TID_OFFSET先不用了解)

构造方法,默认是非公平,也可以设置公平。根据参数来实例化Sync(FairSync和NonFairSync),将Sync的实例赋给 ReadLock 和 WriteLock 的Sync

 

7、重要方法分析

都是一些辅助方法,没有获取、释放锁的方法。因为我们再使用时,都是通过ReentrantReadWriteLock 来获取 ReadLock 和 WriteLock类,然后通过调用 它们的获取释放锁的方法,来间接调用 ReentrantReadWriteLock 里面的 Sync 的方法。

 

8、使用时的方法调用路径

ReadLock 里的方法






 

WriteLock 里的方法


 




 

9、总结

ReentrantReadWriteLock看起来很复杂,实际上它的90%的代码都在Sync自定以同步器里面。FairSync和NonFairSync知识实现了Sync的两个抽象方法(读|写线程是否应该阻塞)。ReentrantReadWriteLock将Sync指向的实例 赋给 ReadLock和WriteLock的内部类Sync,因此ReadLock和WriteLock里面的放啊实际上就是调用 Sync 自定义同步器里面的方法。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值