同步器 ReentrantLock & ReetrantReadWriteLock
前言
前面已经介绍了 AQS(抽象队列同步器)的实现 https://www.cnblogs.com/hysxm/p/14702030.html,本片文章主要对常用的几种同步器做介绍,并对比各自的特点,熟悉其使用场景,主要包含:
- ReentrantLock :可重入独占锁
一、ReentrantLock
ReentrantLock是可重入的独占锁,同时只有一个线程可以获取该锁,其他获取该锁的线程会进入 AQS 的阻塞队列
从 ReentrantLock 的UML类图可以看出,其最终还是通过实现 AQS 实现的,不熟悉AQS的可以先学习下,会对ReentrantLock 的理解有很大的帮助:https://www.cnblogs.com/hysxm/p/14702030.html
ReentrantLock 可以通过构造函数选择公平锁 (FairSync) 与非公平锁 (NonfairSync),两者都是继承自 Sync,而Sync 是实现AbstractQueuedSynchronizer 的抽象类。
ReetrantLock 中 AQS的 state 值代表锁的可重入次数,state=0代表没有任何线程持有;当线程第一次获取资源时,会将state设置为1;如果在没有释放的情况下,持有资源的线程再次进入锁,state+=1,此时state=2 代表锁的可重入次数。释放锁时,使用CAS对state减一,当state=0时,表明当前线程释放锁。
下面分别从 lock() 和 unLock() 分析 ReetrantLock 获取和释放锁的实现
1.1、lock() 获取锁
NonfairSync 非公平锁获取方式
final void lock() { if (compareAndSetState(0, 1)) // CAS设置state=1,成功代表获取到锁,并将锁的拥有者设置为当前线程 setExclusiveOwnerThread(Thread.currentThread()); else // 失败,调用AQS的独占锁获取锁的方式 acquire(1); } // AQS的独占锁资源获取方法,AQS一文中已经详细解读,不再赘述 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } // 最终调用了Sync中 实现的非公平资源获取方法 final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { // 资源空闲可以尝试获取 if (compareAndSetState(0, acquires)) { // 尝试使用CAS设置state值为1 setExclusiveOwnerThread(current); // 获取锁成功 return true; // 获取资源成功 } } // state != 0,当前线程是资源的占有者 => 重入锁,并设置state += 1 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
FairSync 公平锁获取方式
// 加锁 final void lock() { acquire(1); // 调用 AQS的独占锁资源获取方法 } // AQS的独占锁资源获取方法,AQS一文中已经详细解读,不再赘述 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); // 获取当前资源值 if (c == 0) { // 资源空闲可以尝试获取 // 是否有其他线程早于当前线程 请求资源 if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { // CAS 设置 state值为 1,标识占有资源 setExclusiveOwnerThread(current); // 设置独占锁资源拥有者为当前线程 return true; // 获取资源成功 } } // state != 0,当前线程是资源的占有者 => 重入锁,并设置state += 1 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
注意:lock() 、nonfairTryAcquire() 方法是final修饰的,不可以继承修改。原因在于:ReetrantLock的重入锁机制,state的值代表了可重入次数,如果可以修改,就会破坏ReetrantLock 的实现。
AbstractOwnableSynchronizer 中的 exclusiveOwnerThread 属性,作为排它锁拥有者标记,通过该方式判断是否是当前线程持有锁,进而实现可重入的功能。
ReetrantLock公平锁获取锁的方式和非公平的基本一致,只是公平锁在CAS设置state值之前先校验了是否有在排队的前驱结点,而非公平锁是上来直接取尝试获取锁。
1.2、unlock()释放锁
public void unlock() { sync.release(1); // 释放资源 1 } // AQS 独占模式释放资源方法 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } // ReentrantLock 释放资源的实现 protected final boolean tryRelease(int releases) { int c = getState() - releases; // 当前state - releases(1) if (Thread.currentThread() != getExclusiveOwnerThread()) // 当前线程没有持有锁,异常 throw new IllegalMonitorStateException(); boolean free = false; // 是否完全释放资源,释放锁 if (c == 0) { // 所有重入锁释放 free = true; setExclusiveOwnerThread(null); // 重置锁的占有者 } setState(c); // 重新设置state的值 return free; }
ReentrantLock 释放锁,就是对state值进行减一操作,如果state=0,说明所有的重入锁已经释放,锁不再被当前线程持有。
1.3、小结
ReentrantLock 内部通过继承 AQS 实现了公平锁和非公平锁,ReentrantLock 自定义同步器只是简单的实现了 AQS定义的获取和释放资源的(操作state)抽象方法,对于多线程的竞争&排队等全部是AQS实现了,由此可见AQS功能的强大。因为其限定了获取和释放锁的时候,state的值加一减一,所以 ReentrantLock 中state的含义是可重入的次数。
二、ReetrantReadWriteLock
ReentrantReadWriteLock 和 ReentrantLock一样都是可重入锁,其内部也实现了公平锁和不公平锁;不同的是,ReentrantReadWriteLock 按照操作的类型不同分为了读锁和写锁。
源码结构如下
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable { /** 读锁 */ private final ReentrantReadWriteLock.ReadLock readerLock; /** 写锁 */ private final ReentrantReadWriteLock.WriteLock writerLock; /** 内部同步类,实现AQS */ final Sync sync; /** 默认构造方法,非公平 */ public ReentrantReadWriteLock() { this(false); } /** 公平锁构造方法 */ public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); readerLock = new ReadLock(this); writerLock = new WriteLock(this); } /** 内部同步类,实现AQS */ 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{} }
从上面的UML类图和源码结构可以清楚的了解 ReetrantReadWriteLock 的层次,下面按照使用的方式从源码分析其实现原理。
分析读写锁的源码之前,先来看下ReetrantReadWriteLock 中 Sync 的结构,该类和 ReentrantLock 的Sync 不同。
2.1、Sync
1、成员属性
// 共享的位移量 高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;
2、内部类:ThreadLocalHoldCounter & HoldCounter
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { public HoldCounter initialValue() { return new HoldCounter(); } } static final class HoldCounter { int count = 0; // 使用id,而不是引用,是为了防止垃圾存留 final long tid = getThreadId(Thread.currentThread()); }
3、构造函数
Sync() { // 初始化本地线程计数器 readHolds = new ThreadLocalHoldCounter(); setState(getState()); // ensures visibility of readHolds }
2.2、ReadLock
2.2.1、首先看下读锁获取资源的实现 readLock.lock()
public void lock() { sync.acquireShared(1); } public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); } protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); // 存在独占锁 并且不是当前线程拥有锁,则失败 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; int r = sharedCount(c); // 读锁不需要阻塞,读锁的计数小于最大值,并且CAS设置state成功 if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { if (r == 0) { // 锁计数为0,代表是第一个读,设置firstReader为当前线程 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // 锁计数不为0,并且当前线程是第一个读线程 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); }
读锁的资源获取首先要校验读锁是否需要阻塞,分要分为两种强情况:1、非公平锁:校验第一个入队的线程不是排他的 2、公平锁:没有正在等待获取资源的前置线程
然后还会校验读锁的计数小于最大值,以及CAS设置state值成功。如果上述条件不成立,就会进入 fullTryAcquireShared 函数:
// 完整版本的读锁获取资源,主要为了处理 CAS 错误和 tryAcquireShared 中没有处理的重入读 final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; for (;;) { // 自旋 int c = getState(); if (exclusiveCount(c) != 0) { // 有独占锁 if (getExclusiveOwnerThread() != current) // 独占锁不是当前线程,获取资源失败 return -1; } else if (readerShouldBlock()) { // 读锁需要阻塞 // 获取读锁的第一个线程和当前线程相同,继续向下执行 if (firstReader == current) { // assert firstReaderHoldCount > 0; } else { // if (rh == null) { // 前一个获取读锁的线程为空 rh = cachedHoldCounter; // 赋值上一个获取读锁的holdCounter // 如果 holdCounter为空或者holdCounter的线程不是当前线程,则holdCounter赋值为当前线程 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 设置state值 if (sharedCount(c) == 0) { // 没有读锁,则初始化 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // 当前线程是第一个获取读锁的线程,则firstReaderHolderCount + 1 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; // cache for release } return 1; } } }
2.2.2、读锁释放资源 readLock.unlock()
// ReentrantReadWriteLock.ReadLock public void unlock() { sync.releaseShared(1); } // AQS public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; } // ReentrantReadWriteLock.Sync protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); if (firstReader == current) { // 第一个获取读锁的线程是当前线程 if (firstReaderHoldCount == 1) // 如果计数为1,重置 firstReader firstReader = null; else // 如果计数大于1,读锁计数减一 firstReaderHoldCount--; } 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; } for (;;) { // 自旋直到 CAS 设置 state 读锁标识减读锁单元成功 int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) return nextc == 0; } }
读锁释放资源也是通过 自旋 + CAS state 的方式实现的,采用的是 Sync 中默认的实现。
2.3、WriteLock
2.3.1、获取锁 writeLock.lock()
省略之前的调用AQS的代码,直接看下 tryAcquire(int) 的实现,WriteLock使用的是 ReentrantReadWriteLock.Sync.tryAcquire(int) 方法
/* * 流程: * 1. 如果读数量不为0或者写数量不为0,则失败 * 2. 如果写的计数达到了最大值,则失败 * 3. 否则,如果当前线程是重入请求或者满足排队策略,则表明锁是合适的 */ protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); int c = getState(); int w = exclusiveCount(c); // 排它锁计数 if (c != 0) { // 有读锁或者当前线程不是拥有独占锁线程 // (Note: if c != 0 and w == 0 then shared count != 0) if (w == 0 || current != getExclusiveOwnerThread()) return false; if (w + exclusiveCount(acquires) > MAX_COUNT) // 独占锁计数达到最大值,抛出异常 throw new Error("Maximum lock count exceeded"); // Reentrant acquire 重入锁获取资源 setState(c + acquires); return true; } // 写锁是否需要阻塞,FairSync需要判断有没有前驱结点等待 NonfairSync 不需要等待 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; setExclusiveOwnerThread(current); // 设置独占锁线程为当前线程 return true; }
可以看到 writerShouldBlock() 在公平锁和非公平锁中有不同的实现。
2.3.2、释放锁 WriteLock.unlock()
同样的跳过 AQS 中的代码,直接看 ReentrantReadWriteLock 中 tryRelease() 的实现
protected final boolean tryRelease(int releases) { // isHeldExclusively 判断当前线程是否是拥有独占锁的线程 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; boolean free = exclusiveCount(nextc) == 0; if (free) // 所有的排它锁都已经释放 setExclusiveOwnerThread(null); setState(nextc); // CAS 设置 state return free; }
2.4、小结
ReentrantReadWriteLock 中的读锁和写锁实现也是依赖于AQS框架的,读锁是共享锁,写锁是排它锁。读锁获取的时候,如果有写锁则获取失败;写锁获取的时候,如果有读锁或者其他线程拥有了写锁,则失败。