1. 简介
ReentrantLock,可重入锁,是一种递归无阻塞的同步机制。它可以等同于 synchronized
的使用,但是 ReentrantLock 提供了比 synchronized
更强大、灵活的锁机制,可以减少死锁发生的概率。
API 介绍如下:
一个可重入的互斥锁定 Lock,它具有与使用
synchronized
方法和语句所访问的隐式监视器锁定相同的一些基本行为和语义,但功能更强大。ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用
#isHeldByCurrentThread()
和#getHoldCount()
方法来检查此情况是否发生。
ReentrantLock 还提供了公平锁和非公平锁的选择,通过构造方法接受一个可选的 fair
参数(默认非公平锁):当设置为 true 时,表示公平锁;否则为非公平锁。
公平锁与非公平锁的区别在于,公平锁的锁获取是有顺序的。但是公平锁的效率往往没有非公平锁的效率高,在许多线程访问的情况下,公平锁表现出较低的吞吐量。
ReentrantLock 整体结构如下图:
- ReentrantLock 实现 Lock 接口,基于内部的 Sync 实现。
- Sync 实现 AQS ,提供了 FairSync 和 NonFairSync 两种实现。
2. Sync 抽象类
Sync 是 ReentrantLock 的内部静态类,实现 AbstractQueuedSynchronizer 抽象类,同步器抽象类。它使用 AQS 的 state
字段,来表示当前锁的持有数量,从而实现可重入的特性。
2.1 lock
/** * Performs {@link Lock#lock}. The main reason for subclassing * is to allow fast path for nonfair version. */ abstract void lock(); |
- 执行锁。抽象了该方法的原因是,允许子类实现快速获得非公平锁的逻辑。
2.2 nonfairTryAcquire
#nonfairTryAcquire(int acquires)
方法,非公平锁的方式获得锁。代码如下:
final boolean nonfairTryAcquire(int acquires) { //当前线程 final Thread current = Thread.currentThread(); //获取同步状态 int c = getState(); //state == 0,表示没有该锁处于空闲状态 if (c == 0) { //获取锁成功,设置为当前线程所有 if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //线程重入 //判断锁持有的线程是否为当前线程 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; } |
- 该方法主要逻辑:首先判断同步状态
state == 0
?- 如果是,表示该锁还没有被线程持有,直接通过CAS获取同步状态。
- 如果成功,返回 true 。
- 否则,返回 false 。
- 如果不是,则判断当前线程是否为获取锁的线程?
- 如果是,则获取锁,成功返回 true 。成功获取锁的线程,再次获取锁,这是增加了同步状态
state
。通过这里的实现,我们可以看到上面提到的 “它使用 AQS 的state
字段,来表示当前锁的持有数量,从而实现可重入的特性”。 - 否则,返回 false 。
- 如果是,则获取锁,成功返回 true 。成功获取锁的线程,再次获取锁,这是增加了同步状态
- 如果是,表示该锁还没有被线程持有,直接通过CAS获取同步状态。
- 理论来说,这个方法应该在子类 FairSync 中实现,但是为什么会在这里呢?在下文的
ReentrantLock.tryLock()
中,详细解析。
2.3 tryRelease
#tryRelease(int releases)
实现方法,释放锁。代码如下:
protected final boolean tryRelease(int releases) { // 减掉releases int c = getState() - releases; // 如果释放的不是持有锁的线程,抛出异常 if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; // state == 0 表示已经释放完全了,其他线程可以获取同步状态了 if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } |
- 通过判断判断是否为获得到锁的线程,保证该方法线程安全。
- 只有当同步状态彻底释放后,该方法才会返回 true 。当
state == 0
时,则将锁持有线程设置为 null ,free= true
,表示释放成功。
2.4 其他实现方法
其他实现方法比较简单,胖友自己看。
// 是否当前线程独占 @Override protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } // 新生成条件 final ConditionObject newCondition() { return new ConditionObject(); } // Methods relayed from outer class // 获得占用同步状态的线程 final Thread getOwner() { return getState() == 0 ? null : getExclusiveOwnerThread(); } // 获得当前线程持有锁的数量 final int getHoldCount() { return isHeldExclusively() ? getState() : 0; } // 是否被锁定 final boolean isLocked() { return getState() != 0; } /** * Reconstitutes the instance from a stream (that is, deserializes it). * 自定义反序列化逻辑 */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); setState(0); // reset to unlocked state } |
- 从这些方法中,我们可以看到,ReentrantLock 是独占获取同步状态的模式。
3. Sync 实现类
3.1 NonfairSync
NonfairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,非公平锁实现类。
3.1.1 lock
#lock()
实现方法,首先基于 AQS state
进行 CAS 操作,将 0 => 1
。若成功,则获取锁成功。若失败,执行 AQS 的正常的同步状态获取逻辑。代码如下:
@Override final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); } |
- 优先基于 AQS
state
进行 CAS 操作,已经能体现出非公平锁的特点。因为,此时有可能有 N + 1 个线程正在获得锁,其中 1 个线程已经获得到锁,释放的瞬间,恰好被新的线程抢夺到,而不是排队的 N 个线程。
3.1.2 tryAcquire
#tryAcquire(int acquires)
实现方法,非公平的方式,获得同步状态。代码如下:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } |
- 直接调用
#nonfairTryAcquire(int acquires)
方法,非公平锁的方式获得锁。
3.2 FairSync
FairSync 是 ReentrantLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。
3.2.1 lock
#lock()
实现方法,代码如下:
final void lock() { acquire(1); } |
- 直接执行 AQS 的正常的同步状态获取逻辑。
3.2.2 tryAcquire
#tryAcquire(int acquires)
实现方法,公平的方式,获得同步状态。代码如下:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && // <1> compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } |
比较非公平锁和公平锁获取同步状态的过程,会发现两者唯一的区别就在于,公平锁在获取同步状态时多了一个限制条件 <1>
处的 #hasQueuedPredecessors()
方法,是否有前序节点,即自己不是首个等待获取同步状态的节点。代码如下:
// AbstractQueuedSynchronizer.java public final boolean hasQueuedPredecessors() { Node t = tail; //尾节点 Node h = head; //头节点 Node s; //头节点 != 尾节点 //同步队列第一个节点不为null //当前线程是同步队列第一个节点 return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); } |
- 该方法主要做一件事情:主要是判断当前线程是否位于 CLH 同步队列中的第一个。如果是则返回 true ,否则返回 false 。
3. Lock 接口
java.util.concurrent.locks.Lock
接口,定义方法如下:
void lock(); void lockInterruptibly() throws InterruptedException; boolean tryLock(); boolean tryLock(long time, TimeUnit unit) throws InterruptedException; void unlock(); Condition newCondition(); |
-
每个方法解释如下图:
FROM 《Java并发编程的艺术》的 「5.1 Lock 接口」 章节。
4. ReentrantLock
java.util.concurrent.locks.ReentrantLock
,实现 Lock 接口,重入锁。
ReentrantLock 的实现方法,基本是对 Sync 的调用。
4.1 构造方法
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } |
- 基于
fair
参数,创建 FairSync 还是 NonfairSync 对象。
4.2 lock
@Override public void lock() { sync.lock(); } |
4.3 lockInterruptibly
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } |
4.4 tryLock
/** * Acquires the lock only if it is not held by another thread at the time * of invocation. * * <p>Acquires the lock if it is not held by another thread and * returns immediately with the value {@code true}, setting the * lock hold count to one. Even when this lock has been set to use a * fair ordering policy, a call to {@code tryLock()} <em>will</em> * immediately acquire the lock if it is available, whether or not * other threads are currently waiting for the lock. * This "barging" behavior can be useful in certain * circumstances, even though it breaks fairness. If you want to honor * the fairness setting for this lock, then use * {@link #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) } * which is almost equivalent (it also detects interruption). * * <p>If the current thread already holds this lock then the hold * count is incremented by one and the method returns {@code true}. * * <p>If the lock is held by another thread then this method will return * immediately with the value {@code false}. * * @return {@code true} if the lock was free and was acquired by the * current thread, or the lock was already held by the current * thread; and {@code false} otherwise */ @Override public boolean tryLock() { return sync.nonfairTryAcquire(1); } |
- 详细的说明,胖友可以看上面的英文注释。
- 老艿艿的简单理解是:
#tryLock()
实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为fair = true
( 使用公平锁 ),依然调用Sync#nonfairTryAcquire(int acquires)
方法。- 如果真的希望
#tryLock()
还是按照是否公平锁的方式来,可以调用#tryLock(0, TimeUnit)
方法来实现。
4.5 tryLock
@Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } |
4.6 unlock
@Override public void unlock() { sync.release(1); } |
4.7 newCondition
@Override public Condition newCondition() { return sync.newCondition(); } |
4.8 其他实现方法
其他实现方法比较简单,胖友自己看。
public int getHoldCount() { return sync.getHoldCount(); } public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } public boolean isLocked() { return sync.isLocked(); } public final boolean isFair() { return sync instanceof FairSync; } protected Thread getOwner() { return sync.getOwner(); } public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } public final boolean hasQueuedThread(Thread thread) { return sync.isQueued(thread); } public final int getQueueLength() { return sync.getQueueLength(); } protected Collection<Thread> getQueuedThreads() { return sync.getQueuedThreads(); } public boolean hasWaiters(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); } public int getWaitQueueLength(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); } protected Collection<Thread> getWaitingThreads(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); } |
5. ReentrantLock 与 synchronized 的区别
前面提到 ReentrantLock 提供了比 synchronized
更加灵活和强大的锁机制,那么它的灵活和强大之处在哪里呢?他们之间又有什么相异之处呢?
首先他们肯定具有相同的功能和内存语义。
- 与
synchronized
相比,ReentrantLock提供了更多,更加全面的功能,具备更强的扩展性。例如:时间锁等候,可中断锁等候,锁投票。 - ReentrantLock 还提供了条件 Condition ,对线程的等待、唤醒操作更加详细和灵活,所以在多个条件变量和高度竞争锁的地方,ReentrantLock 更加适合(以后会阐述Condition)。
- ReentrantLock 提供了可轮询的锁请求。它会尝试着去获取锁,如果成功则继续,否则可以等到下次运行时处理,而
synchronized
则一旦进入锁请求要么成功要么阻塞,所以相比synchronized
而言,ReentrantLock会不容易产生死锁些。 - ReentrantLock 支持更加灵活的同步代码块,但是使用
synchronized
时,只能在同一个synchronized
块结构中获取和释放。注意,ReentrantLock 的锁释放一定要在finally
中处理,否则可能会产生严重的后果。 - ReentrantLock 支持中断处理,且性能较
synchronized
会好些。
1. 简介
重入锁 ReentrantLock 是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而,读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。
读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:
- 在同一时间,可以允许多个读线程同时访问。
- 但是,在写线程访问时,所有读线程和写线程都会被阻塞。
读写锁的主要特性:
- 公平性:支持公平性和非公平性。
- 重入性:支持重入。读写锁最多支持 65535 个递归写入锁和 65535 个递归读取锁。
- 锁降级:遵循获取写锁,再获取读锁,最后释放写锁的次序,如此写锁能够降级成为读锁。
2. ReadWriteLock
java.util.concurrent.locks.ReadWriteLock
,读写锁接口。定义方法如下:
Lock readLock(); Lock writeLock(); |
- 一对方法,分别获得读锁和写锁 Lock 对象。
3. ReentrantReadWriteLock
java.util.concurrent.locks.ReentrantReadWriteLock
,实现 ReadWriteLock 接口,可重入的读写锁实现类。在它内部,维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 Writer 线程,读取锁可以由多个 Reader 线程同时保持。也就说说,写锁是独占的,读锁是共享的。
ReentrantReadWriteLock 类的大体结构如下:
/** 内部类 读锁 */ 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); } /** 返回用于写入操作的锁 */ @Override public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; } /** 返回用于读取操作的锁 */ @Override public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; } abstract static class Sync extends AbstractQueuedSynchronizer { /** * 省略其余源代码 */ } public static class WriteLock implements Lock, java.io.Serializable { /** * 省略其余源代码 */ } public static class ReadLock implements Lock, java.io.Serializable { /** * 省略其余源代码 */ } |
- ReentrantReadWriteLock 与 ReentrantLock一样,其锁主体也是 Sync,它的读锁、写锁都是通过 Sync 来实现的。所以 ReentrantReadWriteLock 实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样。
- 它的读写锁对应两个类:ReadLock 和 WriteLock 。这两个类都是 Lock 的子类实现。
在 ReentrantLock 中,使用 Sync ( 实际是 AQS )的 int
类型的 state
来表示同步状态,表示锁被一个线程重复获取的次数。但是,读写锁 ReentrantReadWriteLock 内部维护着一对读写锁,如果要用一个变量维护多种状态,需要采用“按位切割使用”的方式来维护这个变量,将其切分为两部分:高16为表示读,低16为表示写。
分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过位运算。假如当前同步状态为S,那么:
- 写状态,等于
S & 0x0000FFFF
(将高 16 位全部抹去) - 读状态,等于
S >>> 16
(无符号补 0 右移 16 位)。
代码如下:
// Sync.java static final int SHARED_SHIFT = 16; // 位数 static final int SHARED_UNIT = (1 << SHARED_SHIFT); static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1; // 每个锁的最大重入次数,65535 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; } |
#exclusiveCount(int c)
静态方法,获得持有写状态的锁的次数。#sharedCount(int c)
静态方法,获得持有读状态的锁的线程数量。不同于写锁,读锁可以同时被多个线程持有。而每个线程持有的读锁支持重入的特性,所以需要对每个线程持有的读锁的数量单独计数,这就需要用到 HoldCounter 计数器。详细解析,见 「6. HoldCounter」 。
划分如下图:
FROM 《Java并发编程的艺术》的 「5.4 读写锁」 章节
3.1 构造方法
在上面的构造方法中,我们已经看到基于 fair
参数,创建 创建 FairSync 还是 NonfairSync 对象。
3.2 getThreadId
#getThreadId(Thread thread)
静态方法,获得线程编号。代码如下:
/** * Returns the thread id for the given thread. We must access * this directly rather than via method Thread.getId() because * getId() is not final, and has been known to be overridden in * ways that do not preserve unique mappings. */ static final long getThreadId(Thread thread) { return UNSAFE.getLongVolatile(thread, TID_OFFSET); } // Unsafe mechanics 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); } } |
-
按道理说,直接调用线程对应的
Thread#getId()
方法,代码如下:private long tid; public long getId() { return tid; }
- 但是实际上,Thread 的这个方法是非
final
修饰的,也就是说,如果我们有实现 Thread 的子类,完全可以覆写这个方法,所以可能导致无法获得tid
属性。因此上面的方法,使用 Unsafe 直接获得tid
属性。不愧是 JDK 的源码,细思极恐。 - 另外,JDK-6346938 也讨论了 “java.lang.Thread.getId() should be final” 这个问题,目前已经被 JDK 认为是一个 BUG ,但是不造为什么一直没修复。
- 但是实际上,Thread 的这个方法是非
3.3 其他实现方法
ReentrantReadWriteLock 的实现方法,主要是调用 Sync 对应的方法,所以重点还是看下面部分的文章。
老艿艿:可以稍微看下,然后跳过先看下面部分的内容。
public final boolean isFair() { return sync instanceof FairSync; } public int getReadLockCount() { return sync.getReadLockCount(); } public boolean isWriteLocked() { return sync.isWriteLocked(); } public boolean isWriteLockedByCurrentThread() { return sync.isHeldExclusively(); } public int getReadHoldCount() { return sync.getReadHoldCount(); } protected Collection<Thread> getQueuedWriterThreads() { return sync.getExclusiveQueuedThreads(); } protected Collection<Thread> getQueuedReaderThreads() { return sync.getSharedQueuedThreads(); } public final boolean hasQueuedThreads() { return sync.hasQueuedThreads(); } public final boolean hasQueuedThread(Thread thread) { return sync.isQueued(thread); } public final int getQueueLength() { return sync.getQueueLength(); } protected Collection<Thread> getQueuedThreads() { return sync.getQueuedThreads(); } public boolean hasWaiters(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition); } public int getWaitQueueLength(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitQueueLength((AbstractQueuedSynchronizer.ConditionObject)condition); } protected Collection<Thread> getWaitingThreads(Condition condition) { if (condition == null) throw new NullPointerException(); if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject)) throw new IllegalArgumentException("not owner"); return sync.getWaitingThreads((AbstractQueuedSynchronizer.ConditionObject)condition); } |
4. 读锁和写锁
在上文中,我们也提到,ReentrantReadWriteLock 的读锁和写锁,基于它内部的 Sync 实现,所以具体的实现方法,就是对内部的 Sync 的方法的调用。
4.1 ReadLock
ReadLock 是 ReentrantReadWriteLock 的内部静态类,实现 java.util.concurrent.locks.Lock
接口,读锁实现类。
4.1.1 构造方法
private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } |
sync
字段,通过 ReentrantReadWriteLock 的构造方法,传入并使用它的 Sync 对象。
4.1.2 lock
@Override public void lock() { sync.acquireShared(1); } |
- 调用 AQS 的
#acquireShared(int arg)
方法,共享式获得同步状态。所以,读锁可以同时被多个线程获取。
4.1.3 lockInterruptibly
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireSharedInterruptibly(1); } |
4.1.4 tryLock
/** * Acquires the read lock only if the write lock is not held by * another thread at the time of invocation. * * <p>Acquires the read lock if the write lock is not held by * another thread and returns immediately with the value * {@code true}. Even when this lock has been set to use a * fair ordering policy, a call to {@code tryLock()} * <em>will</em> immediately acquire the read lock if it is * available, whether or not other threads are currently * waiting for the read lock. This "barging" behavior * can be useful in certain circumstances, even though it * breaks fairness. If you want to honor the fairness setting * for this lock, then use {@link #tryLock(long, TimeUnit) * tryLock(0, TimeUnit.SECONDS) } which is almost equivalent * (it also detects interruption). * * <p>If the write lock is held by another thread then * this method will return immediately with the value * {@code false}. * * @return {@code true} if the read lock was acquired */ @Override public boolean tryLock() { return sync.tryReadLock(); } |
- 详细的说明,胖友可以看上面的英文注释。实际上,原因和 《【死磕 Java 并发】—– J.U.C 之重入锁:ReentrantLock》 的 「4.4 tryLock」 相同。
- 老艿艿的简单理解是:
#tryLock()
实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为fair = true
( 使用公平锁 ),依然调用Sync#tryReadLock()
方法。- 如果真的希望
#tryLock()
还是按照是否公平锁的方式来,可以调用#tryLock(0, TimeUnit)
方法来实现。
4.1.5 tryLock
@Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout)); } |
4.1.6 unlock
@Override public void unlock() { sync.releaseShared(1); } |
- 调用 AQS 的
#releaseShared(int arg)
方法,共享式释放同步状态。
4.1.7 newCondition
@Override public Condition newCondition() { throw new UnsupportedOperationException(); } |
- 不支持 Condition 条件。
4.2 WriteLock
WriteLock 的代码,类似 ReadLock 的代码,差别在于独占式获取同步状态。
WriteLock 是 ReentrantReadWriteLock 的内部静态类,实现 java.util.concurrent.locks.Lock
接口,写锁实现类。
4.2.1 构造方法
private final Sync sync; protected ReadLock(ReentrantReadWriteLock lock) { sync = lock.sync; } |
sync
字段,通过 ReentrantReadWriteLock 的构造方法,传入并使用它的 Sync 对象。
4.2.2 lock
@Override public void lock() { sync.acquire(1); } |
- 调用 AQS 的
#.acquire(int arg)
方法,独占式获得同步状态。所以,写锁只能同时被一个线程获取。
4.2.3 lockInterruptibly
@Override public void lockInterruptibly() throws InterruptedException { sync.acquireInterruptibly(1); } |
4.2.4 tryLock
/** * Acquires the write lock only if it is not held by another thread * at the time of invocation. * * <p>Acquires the write lock if neither the read nor write lock * are held by another thread * and returns immediately with the value {@code true}, * setting the write lock hold count to one. Even when this lock has * been set to use a fair ordering policy, a call to * {@code tryLock()} <em>will</em> immediately acquire the * lock if it is available, whether or not other threads are * currently waiting for the write lock. This "barging" * behavior can be useful in certain circumstances, even * though it breaks fairness. If you want to honor the * fairness setting for this lock, then use {@link * #tryLock(long, TimeUnit) tryLock(0, TimeUnit.SECONDS) } * which is almost equivalent (it also detects interruption). * * <p>If the current thread already holds this lock then the * hold count is incremented by one and the method returns * {@code true}. * * <p>If the lock is held by another thread then this method * will return immediately with the value {@code false}. * * @return {@code true} if the lock was free and was acquired * by the current thread, or the write lock was already held * by the current thread; and {@code false} otherwise. */ @Override public boolean tryLock( ) { return sync.tryWriteLock(); } |
- 详细的说明,胖友可以看上面的英文注释。实际上,原因和 《【死磕 Java 并发】—– J.U.C 之重入锁:ReentrantLock》 的 「4.4 tryLock」 相同。
- 老艿艿的简单理解是:
#tryLock()
实现方法,在实现时,希望能快速的获得是否能够获得到锁,因此即使在设置为fair = true
( 使用公平锁 ),依然调用Sync#tryWriteLock()
方法。- 如果真的希望
#tryLock()
还是按照是否公平锁的方式来,可以调用#tryLock(0, TimeUnit)
方法来实现。
4.2.5 tryLock
@Override public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException { return sync.tryAcquireNanos(1, unit.toNanos(timeout)); } |
4.2.6 unlock
@Override public void unlock() { sync.release(1); } |
- 调用 AQS 的
#release(int arg)
方法,独占式释放同步状态。
4.2.7 newCondition
@Override public Condition newCondition() { return sync.newCondition(); } |
- 调用
Sync#newCondition()
方法,创建 Condition 对象。
4.2.8 isHeldByCurrentThread
public boolean isHeldByCurrentThread() { return sync.isHeldExclusively(); } |
- 调用
Sync#isHeldExclusively()
方法,判断是否被当前线程独占锁。
4.2.9 getHoldCount
public int getHoldCount() { return sync.getWriteHoldCount(); } |
- 调用
Sync#getWriteHoldCount()
方法,返回当前线程独占锁的持有数量。
5. Sync 抽象类
Sync 是 ReentrantReadWriteLock 的内部静态类,实现 AbstractQueuedSynchronizer 抽象类,同步器抽象类。它使用 AQS 的 state
字段,来表示当前锁的持有数量,从而实现可重入和读写锁的特性。
4.1 构造方法
private transient ThreadLocalHoldCounter readHolds; // 当前线程的读锁持有数量 private transient Thread firstReader = null; // 第一个获取读锁的线程 private transient int firstReaderHoldCount; // 第一个获取读锁的重入数 private transient HoldCounter cachedHoldCounter; // 最后一个获得读锁的线程的 HoldCounter 的缓存对象 Sync() { readHolds = new ThreadLocalHoldCounter(); setState(getState()); // ensures visibility of readHolds } |
- 详细解析,见 「6. HoldCounter」 。
4.2 writerShouldBlock
abstract boolean writerShouldBlock(); |
- 获取写锁时,如果有前序节点也获得锁时,是否阻塞。NonefairSync 和 FairSync 下有不同的实现。详细解析,见 「6. Sync 实现类」 。
4.3 readerShouldBlock
abstract boolean readerShouldBlock(); |
- 获取读锁时,如果有前序节点也获得锁时,是否阻塞。NonefairSync 和 FairSync 下有不同的实现。详细解析,见 「6. Sync 实现类」 。
4.4 【写锁】tryAcquire
@Override protected final boolean tryAcquire(int acquires) { Thread current = Thread.currentThread(); //当前锁个数 int c = getState(); //写锁 int w = exclusiveCount(c); if (c != 0) { //c != 0 && w == 0 表示存在读锁 //当前线程不是已经获取写锁的线程 if (w == 0 || current != getExclusiveOwnerThread()) return false; //超出最大范围 if (w + exclusiveCount(acquires) > MAX_COUNT) throw new Error("Maximum lock count exceeded"); setState(c + acquires); return true; } // 是否需要阻塞 if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) return false; //设置获取锁的线程为当前线程 setExclusiveOwnerThread(current); return true; } |
- 该方法和 ReentrantLock 的
#tryAcquire(int arg)
大致一样,差别在判断重入时,增加了一项条件:读锁是否存在。因为要确保写锁的操作对读锁是可见的。如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。 - 调用
#writerShouldBlock()
抽象方法,若返回 true ,则获取写锁失败。
4.5 【读锁】tryAcquireShared
#tryAcqurireShared(int arg)
方法,尝试获取读同步状态,获取成功返回 >= 0
的结果,否则返回 < 0
的结果。代码如下:
protected final int tryAcquireShared(int unused) { //当前线程 Thread current = Thread.currentThread(); int c = getState(); //exclusiveCount(c)计算写锁 //如果存在写锁,且锁的持有者不是当前线程,直接返回-1 //存在锁降级问题,后续阐述 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //读锁 int r = sharedCount(c); /* * readerShouldBlock():读锁是否需要等待(公平锁原则) * r < MAX_COUNT:持有线程小于最大数(65535) * compareAndSetState(c, c + SHARED_UNIT):设置读取锁状态 */ if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) { //修改高16位的状态,所以要加上2^16 /* * holdCount部分后面讲解 */ 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()
方法来判断读锁是否不需要阻塞,读锁持有线程数小于最大值(65535),且 CAS 设置锁状态成功,执行以下代码(对于 HoldCounter ,见 「6. HoldCounter」 中),并返回 1 。如果不满足任一条件,则调用#fullTryAcquireShared(Thread thread)
方法,详细解析,见 「4.5.1 fullTryAcquireShared」 中。
4.5.1 fullTryAcquireShared
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) { } //HoldCounter后面讲解 else { if (rh == null) { rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) { rh = readHolds.get(); if (rh.count == 0) // 计数为 0 ,说明没得到读锁,清空线程变量 readHolds.remove(); } } if (rh.count == 0) // 说明没得到读锁 return -1; } } //读锁超出最大范围 if (sharedCount(c) == MAX_COUNT) throw new Error("Maximum lock count exceeded"); //CAS设置读锁成功 if (compareAndSetState(c, c + SHARED_UNIT)) { //修改高16位的状态,所以要加上2^16 //如果是第1次获取“读取锁”,则更新firstReader和firstReaderHoldCount if (sharedCount(c) == 0) { firstReader = current; firstReaderHoldCount = 1; } //如果想要获取锁的线程(current)是第1个获取锁(firstReader)的线程,则将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; // cache for release } return 1; } } } |
- 该方法会根据“是否需要阻塞等待”,“读取锁的共享计数是否超过限制”等等进行处理。如果不需要阻塞等待,并且锁的共享计数没有超过限制,则通过 CAS 尝试获取锁,并返回 1 。所以,
#fullTryAcquireShared(Thread)
方法,是#tryAcquireShared(int unused)
方法的自旋重试的逻辑。
4.6 【写锁】tryRelease
protected final boolean tryRelease(int releases) { //释放的线程不为锁的持有者 if (!isHeldExclusively()) throw new IllegalMonitorStateException(); int nextc = getState() - releases; //若写锁的新线程数为0,则将锁的持有者设置为null boolean free = exclusiveCount(nextc) == 0; if (free) setExclusiveOwnerThread(null); setState(nextc); return free; } |
- 写锁释放锁的整个过程,和独占锁 ReentrantLock 相似,每次释放均是减少写状态,当写状态为 0 时,表示写锁已经完全释放了,从而让等待的其他线程可以继续访问读、写锁,获取同步状态。同时,此次写线程的修改对后续的线程可见。
4.7 【读锁】tryReleaseShared
protected final boolean tryReleaseShared(int unused) { Thread current = Thread.currentThread(); //如果想要释放锁的线程为第一个获取锁的线程 if (firstReader == current) { //仅获取了一次,则需要将firstReader 设置null,否则 firstReaderHoldCount - 1 if (firstReaderHoldCount == 1) firstReader = null; else firstReaderHoldCount--; } //获取rh对象,并更新“当前线程获取锁的信息” 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; } //CAS更新同步状态 for (;;) { int c = getState(); int nextc = c - SHARED_UNIT; if (compareAndSetState(c, nextc)) return nextc == 0; } } |
-
#unmatchedUnlockException()
方法,返回 IllegalMonitorStateException 异常。代码如下:private IllegalMonitorStateException unmatchedUnlockException() { return new IllegalMonitorStateException( "attempt to unlock read lock, not locked by current thread"); }
- 出现的情况是,unlock 读锁的线程,非获得读锁的线程。正常使用的情况,不会出现该情况。
4.8 tryWriteLock
#tryWriteLock()
方法,尝试获取写锁。
- 若获取成功,返回 true 。
- 若失败,返回 false 即可,不进行等待排队。
代码如下:
final boolean tryWriteLock(){ Thread current = Thread.currentThread(); int c = getState(); if(c != 0){ int w = exclusiveCount(c); // 获得现在写锁获取的数量 if(w == 0 || current != getExclusiveOwnerThread()){ // 判断是否是其他的线程获取了写锁。若是,返回 false return false; } if(w == MAX_COUNT){ // 超过写锁上限,抛出 Error 错误 throw new Error("Maximum lock count exceeded"); } } if(!compareAndSetState(c, c + 1)){ // CAS 设置同步状态,尝试获取写锁。若失败,返回 false return false; } setExclusiveOwnerThread(current); // 设置持有写锁为当前线程 return true; } |
4.9 tryReadLock
#tryReadLock()
方法,尝试获取读锁。
- 若获取成功,返回 true 。
- 若失败,返回 false 即可,不进行等待排队。
代码如下:
final boolean tryReadLock() { Thread current = Thread.currentThread(); for (;;) { int c = getState(); //exclusiveCount(c)计算写锁 //如果存在写锁,且锁的持有者不是当前线程,直接返回-1 //存在锁降级问题,后续阐述 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return false; // 读锁 int r = sharedCount(c); /* * HoldCount 部分后面讲解 */ 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; } } } |
4.10 isHeldExclusively
protected final boolean isHeldExclusively() { // While we must in general read state before owner, // we don't need to do so to check if current thread is owner return getExclusiveOwnerThread() == Thread.currentThread(); } |
4.11 newCondition
final ConditionObject newCondition() { return new ConditionObject(); } |
4.12 其他实现方法
其它实现方法,比较简单,胖友自己查看。
final Thread getOwner() { // Must read state before owner to ensure memory consistency return ((exclusiveCount(getState()) == 0) ? null : getExclusiveOwnerThread()); } final int getReadLockCount() { return sharedCount(getState()); } final boolean isWriteLocked() { return exclusiveCount(getState()) != 0; } final int getWriteHoldCount() { return isHeldExclusively() ? exclusiveCount(getState()) : 0; } 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; } /** * Reconstitutes the instance from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); readHolds = new ThreadLocalHoldCounter(); setState(0); // reset to unlocked state } final int getCount() { return getState(); } |
5. Sync 实现类
5.1 NonfairSync
NonfairSync 是 ReentrantReadWriteLock 的内部静态类,实现 Sync 抽象类,非公平锁实现类。代码如下:
static final class NonfairSync extends Sync { @Override final boolean writerShouldBlock() { return false; // writers can always barge } @Override final boolean readerShouldBlock() { /* As a heuristic to avoid indefinite writer starvation, * block if the thread that momentarily appears to be head * of queue, if one exists, is a waiting writer. This is * only a probabilistic effect since a new reader will not * block if there is a waiting writer behind other enabled * readers that have not yet drained from the queue. */ return apparentlyFirstQueuedIsExclusive(); } } |
-
因为写锁是独占排它锁,所以在非公平锁的情况下,需要调用 AQS 的
#apparentlyFirstQueuedIsExclusive()
方法,判断是否当前写锁已经被获取。代码如下:final boolean apparentlyFirstQueuedIsExclusive() { Node h, s; return (h = head) != null && (s = h.next) != null && !s.isShared() && // 非共享,即独占 s.thread != null; }
5.2 FairSync
FairSync 是 ReentrantReadWriteLock 的内部静态类,实现 Sync 抽象类,公平锁实现类。代码如下:
static final class FairSync extends Sync { @Override final boolean writerShouldBlock() { return hasQueuedPredecessors(); } @Override final boolean readerShouldBlock() { return hasQueuedPredecessors(); } } |
- 调用 AQS 的
#hasQueuedPredecessors()
方法,是否有前序节点,即自己不是首个等待获取同步状态的节点。
6. HoldCounter
在读锁获取锁和释放锁的过程中,我们一直都可以看到一个变量 rh
(HoldCounter ),该变量在读锁中扮演着非常重要的作用。
我们了解读锁的内在机制其实就是一个共享锁,为了更好理解 HoldCounter ,我们暂且认为它不是一个锁的概率,而相当于一个计数器。一次共享锁的操作就相当于在该计数器的操作。获取共享锁,则该计数器 + 1,释放共享锁,该计数器 - 1。只有当线程获取共享锁后才能对共享锁进行释放、重入操作。所以 HoldCounter 的作用就是当前线程持有共享锁的数量,这个数量必须要与线程绑定在一起,否则操作其他线程锁就会抛出异常。
我们先看 HoldCounter 的代码:
HoldCounter 是 Sync 的内部静态类。
static final class HoldCounter { int count = 0; // 计数器 final long tid = getThreadId(Thread.currentThread()); // 线程编号 } |
-
HoldCounter 定义非常简单,就是一个计数器
count
和线程编号tid
两个变量。按照这个意思我们看到 HoldCounter 是需要和某给线程进行绑定了。我们知道如果要将一个对象和线程绑定仅仅有tid
是不够的,而且从上面的代码我们可以看到 HoldCounter 仅仅只是记录了tid
,根本起不到绑定线程的作用。那么怎么实现呢?答案是实现 ThreadLocal 的 ThreadLocalHoldCounter 类,代码如下:ThreadLocalHoldCounter 是 Sync 的内部静态类。
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> { @Override public HoldCounter initialValue() { return new HoldCounter(); } }
通过 ThreadLocalHoldCounter 类,HoldCounter 就可以与线程进行绑定了。故而,HoldCounter 应该就是绑定线程上的一个计数器,而 ThreadLocalHoldCounter 则是线程绑定的 ThreadLocal。从上面我们可以看到 ThreadLocal 将 HoldCounter 绑定到当前线程上,同时 HoldCounter 也持有线程编号,这样在释放锁的时候才能知道 ReadWriteLock 里面缓存的上一个读取线程(cachedHoldCounter
)是否是当前线程。这样做的好处是可以减少ThreadLocal.get()
方法的次调用数,因为这也是一个耗时操作。需要说明的是这样HoldCounter 绑定线程编号而不绑定线程对象的原因是,避免 HoldCounter 和 ThreadLocal 互相绑定而导致 GC 难以释放它们(尽管 GC 能够智能的发现这种引用而回收它们,但是这需要一定的代价),所以其实这样做只是为了帮助 GC 快速回收对象而已。
看到这里我们明白了 HoldCounter 作用了,我们在看一个获取读锁的代码段:
//如果获取读锁的线程为第一次获取读锁的线程,则firstReaderHoldCount重入数 + 1 else if (firstReader == current) { firstReaderHoldCount++; } else { //非firstReader计数 if (rh == null) rh = cachedHoldCounter; //rh == null 或者 rh.tid != current.getId(),需要获取rh if (rh == null || rh.tid != getThreadId(current)) rh = readHolds.get(); //加入到readHolds中 else if (rh.count == 0) readHolds.set(rh); //计数+1 rh.count++; cachedHoldCounter = rh; // cache for release } |
- 这里解释下为何要引入
firstReader
、firstReaderHoldCount
变量。这是为了一个效率问题,firstReader
是不会放入到readHolds
中的,如果读锁仅有一个的情况下,就会避免查找readHolds
。
7. 锁降级
在本文开篇,LZ 就阐述了读写锁有一个特性就是锁降级。锁降级就意味着写锁是可以降级为读锁的,但是需要遵循先获取写锁、获取读锁在释放写锁的次序。注意如果当前线程先获取写锁,然后释放写锁,再获取读锁这个过程不能称之为锁降级,锁降级一定要遵循那个次序。
在获取读锁的方法 #tryAcquireShared(int unused)
中,有一段代码就是来判读锁降级的:
int c = getState(); //exclusiveCount(c)计算写锁 //如果存在写锁,且锁的持有者不是当前线程,直接返回-1 //存在锁降级问题,后续阐述 if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; //读锁 int r = sharedCount(c); |
锁降级中读锁的获取释放为必要?肯定是必要的。试想,假如当前线程 A 不获取读锁而是直接释放了写锁,这个时候另外一个线程 B 获取了写锁,那么这个线程 B 对数据的修改是不会对当前线程 A 可见的。如果获取了读锁,则线程B在获取写锁过程中判断如果有读锁还没有释放则会被阻塞,只有当前线程 A 释放读锁后,线程 B 才会获取写锁成功。