Java并发编程之读写锁 ReentrantReadWriteLock 源码分析
我们知道在解决线程安全问题上使用 ReentrantLock 就可以,但是 ReentrantLock 是独占锁,同时只有一个线程可以获取该锁,而实际情况下会有写少读多的场景,显然 ReentrantLock 满足不了需求,所以 ReentrantReadWriteLock 应运而生,ReentrantReadWriteLock 采用读写分离,多个线程可以同时获取读锁。
首先我们先看一下,ReentrantReadWriteLock 内部构造先看下它的类图结构如下图所示:
如上图可以看到读写锁内部维护了一个ReadLock和WriteLock,并且也提供了公平和非公平的实现,下面只介绍下非公平的读写锁的实现,我们知道AQS里面维护了一个state状态,
而ReentrantReadWriteLock 则需要维护读状态和写状态,一个state是无法表示写和读状态的。ReentrantReadWriteLock 巧妙的使用 state 的高 16 位表示读状态,
也就是获取改读锁的线程个数,低 16 位 表示获取到写锁的线程的可重入次数。并通过CAS对其进行操作实现了读写分离,在读多写少的场景下比较适用。
接下来用一张图来加深对 ReentrantReadWriteLock 的理解:
首先我们先看ReentrantReadWriteLock 的内部类Sync的一些关键属性和方法,源码如下:
static final int SHARED_SHIFT = 16; //共享锁(读锁)状态单位值65536 static final int SHARED_UNIT = (1 << SHARED_SHIFT); //共享锁线程最大个数65535 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; //排它锁(写锁)掩码 二进制 15个1 static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1; //用来记录最后一个获取读锁的线程获取读锁的可重入次数 private transient HoldCounter cachedHoldCounter; //用来记录第一个获取到读锁的线程 private transient Thread firstReader; //用来记录第一个获取到读锁的线程获取读锁的可重入次数 private transient int firstReadHoldCount; //用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数 private transient ThreadLocalHoldCounter readHolds = new ThreadLocalHoldCounter(); /** 返回读锁线程数 */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; } /** 返回写锁可重入个数 */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
类图中 firstReader用来记录第一个获取到读锁的线程,firstReadHoldCount则记录第一个获取到读锁的线程获取读锁的可重入数。cachedHoldCounter用来记录最后一个获取读锁的线程获取读锁的可重入次数。
接下我们进入ReentrantReadWriteLock 的内部类Sync的内部类HoldCounter类的源码,如下:
static final class HoldCounter { int count = 0; //线程id final long tid = getThreadId(Thread.currentThread()); }
readHolds 是ThreadLocal 变量,用来存放除去第一个获取读锁线程外的其他线程获取读锁的可重入次数,可知ThreadLocalHoldCounter继承了ThreadLocal,里面initialValue方法返回一个HoldCounter对象,源码如下: