读写锁是怎样实现分别记录读写状态的?
在 ReentrantLock 中,使用 Sync ( 实际是 AQS )的 int 类型的 state 来表示同步状态,表示锁被一个线程重复获取的次数。但是,读写锁 ReentrantReadWriteLock 内部维护着一对读写锁,如果要用一个变量维护多种状态,需要采用“按位切割使用”的方式来维护这个变量,将其切分为两部分:高16位表示读,低16位表示写。高位表示读锁被持有的数量,低位表示写锁重入数
分割之后,通过位运算确定读锁和写锁的状态呢 ,假如当前同步状态为S,那么:
- 写状态,等于 S & 0x0000FFFF(将高 16 位全部抹去)。 当写状态加1,等于S+1.
- 读状态,等于 S >>> 16 (无符号补 0 右移 16 位)。当读状态加1,等于S+(1<<16),也就是S+0x00010000
根据状态的划分能得出一个推论:S不等于0时,当写状态(S&0x0000FFFF)等于0时,则读状态(S>>>16)大于0,即读锁已被获取。
//切割标志,16为static final int SHARED_SHIFT = 16;static final int SHARED_UNIT = (1 << SHARED_SHIFT);//最大线程数量65535static 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; }
不同于写锁,读锁可以同时被多个线程持有。而每个线程持有的读锁支持重入的特性,所以需要对每个线程持有的读锁的数量单独计数,这就需要用到 HoldCounter 计数器
HoldCounter 计数器
读锁的内在机制其实就是一个共享锁。一次共享锁的操作就相当于对HoldCounter 计数器的操作。获取共享锁,则该计数器 + 1,释放共享锁,该计数器 - 1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。
通过 ThreadLocalHoldCounter 类,HoldCounter 与线程进行绑定。HoldCounter 是绑定线程的一个计数器,而 ThreadLocalHoldCounter 则是线程绑定的 ThreadLocal。
- HoldCounter是用来记录读锁重入数的对象
- ThreadLocalHoldCounter是ThreadLocal变量,用来存放不是第一个获取读锁的线程的其他线程的读锁重入数对象
写锁的获取
写锁是一个支持重进入的排它锁。如果当前线程已经获取了写锁,则增加写状态。如果当前线程在获取写锁时,读锁已经被获取(读状态不为0)或者该线程不是已经获取写锁的线程, 则当前线程进入等待状态。
写锁的获取是通过重写AQS中的tryAcquire方法实现的。
protected final boolean tryAcquire(int acquires) {//当前线程Thread current = Thread.currentThread();//获取state状态,存在读锁或者写锁则不为0int c = getState();//获取写锁的重入数int w = exclusiveCount(c);//c!=0 表示有读锁或者写锁存在if (c != 0) {// (Note: if c != 0 and w == 0 then shared count != 0)// w=0 && c!=0 表示有读锁,读写互斥,当前写锁不允许添加// w!=0 && current != getExclusiveOwnerThread() 表示有写锁存在,//而且当前的线程不是占有写锁的线程,直接返回,写写互斥if (w == 0 || current != getExclusiveOwnerThread())return false;// 超出最大范围 65535if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// 同步state状态,写锁重入数+1setState(c + acquires);return true;}//writerShouldBlock有公平与非公平的实现, 非公平返回false,会尝试通过cas加锁//c==0 写锁未被任何线程获取,当前线程是否阻塞或者cas尝试获取锁if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;//设置写锁为当前线程所有setExclusiveOwnerThread(current);return true;}

通过源码我们可以知道:
- 读写互斥
- 写写互斥
- 写锁支持同一个线程重入
- writerShouldBlock写锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)

写锁的释放
//releases=1protected final boolean tryRelease(int releases) {//当前当前线程,不是拿到锁的线程,抛异常if (!isHeldExclusively())throw new IllegalMonitorStateException();//写锁-1,可能有重入int nextc = getState() - releases;//当前写状态是否为0,为0则释放写锁boolean free = exclusiveCount(nextc) == 0;if (free)setExclusiveOwnerThread(null);setState(nextc);return free;}
读锁的获取
protected final int tryAcquireShared(int unused) {Thread current = Thread.currentThread();int c = getState();//exclusiveCount(c)表示写锁的次数//写锁!=0,而且不是持有锁不是当前线程,返回-1(判断锁降级)if (exclusiveCount(c) != 0 &&getExclusiveOwnerThread() != current)return -1;//计算出读锁的数量int r = sharedCount(c);//读锁是否阻塞readerShouldBlock()//r < MAX_COUNT: 持有读锁的线程小于最大数(65535)//compareAndSetState(c, c + SHARED_UNIT) 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;}// 尝试通过自旋的方式获取读锁,实现了重入逻辑return fullTryAcquireShared(current);}

- 读锁共享,读读不互斥
- 读锁可重入,每个获取读锁的线程都会记录对应的重入数
- 读写互斥,锁降级场景除外
- 支持锁降级,持有写锁的线程,可以获取读锁,但是后续要记得把读锁和写锁读释放
- readerShouldBlock读锁是否阻塞实现取决公平与非公平的策略(FairSync和NonfairSync)
为什么要搞一个firstRea,需要记录第一个线程?是为了一个效率问题,firstReader是不会放入到readHolds中的,如果读锁仅有一个的情况下就会避免查找readHolds。
读锁的释放
protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();//如果当前线程是第一个获取读锁的线程if (firstReader == current) {// assert firstReaderHoldCount > 0;if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;//重入次数减1} else {//不是第一个获取读锁的线程HoldCounter rh = cachedHoldCounter;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;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;}}
