ReentrantReadWriteLock 源码分析
一 总体认识读写锁
读写锁,简单点说
1 如果当前有读锁,获取读锁不阻塞
2 如果当前是读锁,获取写锁阻塞
3 如果当前是写锁,同一个线程获取读锁不阻塞
4 当前是读锁,无论是否同一线程获取读锁和写锁都阻塞
5 写锁支持Condition 读锁不支持
引用下别人总结的
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。 (2)重进入:读锁和写锁都支持线程重进入。 (3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
二 关键属性
在分析源码的方法前,先说明下关键的字段有助于理解方法
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { /** 读锁 */ private final ReentrantReadWriteLock.ReadLock readerLock; /** 写锁 */ private final ReentrantReadWriteLock.WriteLock writerLock; final Sync sync; /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */ public ReentrantReadWriteLock() { this(false); } /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */ 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; } abstract static class Sync extends AbstractQueuedSynchronizer {} static final class NonfairSync extends Sync {} static final class FairSync extends Sync {} public static class ReadLock implements Lock, java.io.Serializable {} public static class WriteLock implements Lock, java.io.Serializable {} }
和重入锁一样,都是内部有一个AQS的实现类,然后事情都交给它去干,这种组合方式。所以呢,关键点就在于 abstract static class Sync extends AbstractQueuedSynchronizer
三 Sync源码分析
Sync类内部存在两个内部类,分别为HoldCounter和ThreadLocalHoldCounter。HoldCounter的意义是记录每个线程读锁的重入次数
static final class HoldCounter { // 计数 int count = 0; // Use id, not reference, to avoid garbage retention // 获取当前线程的TID属性的值 final long tid = getThreadId(Thread.currentThread()); }
继承了ThreadLocal的 ThreadLocalHoldCounter用来保存每个线程的读锁重入次数
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { // 重写初始化方法,在没有进行set的情况下,获取的都是该HoldCounter值 public HoldCounter initialValue() { return new HoldCounter(); } }
static final int SHARED_SHIFT = 16;//高16位是读锁 static final int SHARED_UNIT = (1 << SHARED_SHIFT);//相当于1后面16个0,每次读锁重入一次就加这个数 static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;//高16位都是0低16位都是1 /** Returns the number of shared holds represented in count */ static int sharedCount(int c) { return c >>> SHARED_SHIFT; }//读锁的重入次数 /** Returns the number of exclusive holds represented in count */ static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }//计算写锁的重入次数
下面按照排他锁,到重入锁的顺序依次分析 ,先来看锁的获取
排他锁
protected final boolean tryAcquire(int acquires) { 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;//当前线程包成Node进入同步队列中阻塞 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); // Reentrant acquire setState(c + acquires);//增加重入次数 return true; } if (writerShouldBlock() || !compareAndSetState(c, c + acquires))//这个分支说明c==0,那就通过cas的方法抢锁 writeShouldBlock是公平锁才会实现的,主要检查同步队列中如果有线程等待,当前线程就进队列去排队 return false; setExclusiveOwnerThread(current); return true; }
从上面的分析,发现了吧和重入锁并没有多大的差别,只不多增加了检查读锁重入数的逻辑其他的都一样的
再来是锁的释放
protected final boolean tryRelease(int releases) { if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases;//释放一定是在持有锁情况下,所以不需要用CAS boolean free = exclusiveCount(nextc) == 0;//排他锁是否等于0 if (free)//如果等于0,释放锁 setExclusiveOwnerThread(null); setState(nextc); return free;//返回true,就会执行AQS中的释放逻辑,唤醒 }
释放逻辑也不难,就是判断排他锁的重入数是否等于0,如果等于0,返回true,执行释放逻辑
final boolean tryWriteLock() { Thread current = Thread.currentThread(); int c = getState(); if (c != 0) { int w = exclusiveCount(c); if (w == 0 || current != getExclusiveOwnerThread())//写锁数是0那就说明有读锁 return false; if (w == MAX_COUNT) throw new Error("Maximum lock count exceeded"); } if (!compareAndSetState(c, c + 1))//CAS失败说明有别的线程改动了state return false; setExclusiveOwnerThread(current); return true; }
接下来是共享锁部分
共享锁
获取共享锁逻辑
protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)//如果此时排他锁不为0,并且持有锁的线程不是当前线程,返回-1。这也说明了如果持有锁的线程是当前线程就能获得读锁了 return -1; int r = sharedCount(c);//当前的读锁总数量 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {//CAS方式读锁总量加1 if (r == 0) {//如果此时没有任何读锁 firstReader = current;//给firstReader赋值 firstReaderHoldCount = 1; } else if (firstReader == current) { firstReaderHoldCount++; } else {//先从ThreadLocal里取出来重入数,再加1 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);//这个是由于上面的if不满足而进入的,其实逻辑和上面大差不差的。不满足可能是由于CAS的时候失败了,此方法会通过自旋的方式执行上面的逻辑 }
释放共享锁逻辑
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); if (firstReader == current) {//还是处理firstReader问题 // assert firstReaderHoldCount > 0; if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } else { HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current))//如果不是缓存的计数器,那么就从Threadlocal里拿 rh = readHolds.get(); int count = rh.count; if (count <= 1) {//如果小于1了,释放后就是0了,这时就要清理ThreadLocal readHolds.remove(); if (count <= 0) throw unmatchedUnlockException(); } --rh.count; } for (;;) {//上面的都是处理每个线程的读锁计数问题的,都不是什么关键流程 int c = getState(); int nextc = c - SHARED_UNIT;//共享锁计数-1 if (compareAndSetState(c, nextc))//通过CAS设置新值 // 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;//如果-1之后等于0,那就是说明应该释放锁了 } }
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; } } }
四 总结
源码分析下来,觉得读写锁如果之前的基础打好了,理解起来并不难!