ReentrantReadWriterLock这个类顾名思义就是一个同时支持读锁与写锁的可重入类。跟ReentrantLock相比不仅多了读锁的可共享锁,还对读与写进行了分类,在内部对其进行协调(读锁升级为写锁、写锁降级为读锁)。另外读写锁最多支持2^16-1
个递归写入锁和2^16-1
个递归读取锁,相比ReentrantLock的2^32-1
少了很多,原因则是ReentrantReadWriterLock使用了state的高位16位表示共享锁的数量,低位16位表示独占锁可重入的数量。
写锁是独占锁,也叫排它锁,当某一线程使用了写锁时,其他锁(包括共享锁)不能够操作被锁住的资源。而读锁则可以由多个读锁共享资源。两种所不能够同时使用。总的来说则是:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操作,而只有少量的写操作(修改数据)
写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性而读取锁是不能直接升级为写入锁的。因为获取一个写入锁需要释放所有读取锁,释放后获得写入锁即实现了锁升级的特性。
由于这里实现的内部类较多,先放一个表格:
类 | 作用 |
---|---|
Sync, | 继承AQS,锁功能的主要实现者 |
FairSync | 继承Sync,主要实现公平锁 |
NofairSync | 继承Sync,主要实现非公平锁 |
ReadLock | 读锁,通过sync代理实现锁功能 |
WriteLock | 写锁,通过sync代理实现锁功能 |
一些基础的代码:
/** 内部类 读锁 */
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 {
/**
* 省略其余源代码
*/
}
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实现。
在里面读写锁通过位运算迅速确定读锁和写锁的状态。代码如下:
// 以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;
// 将状态位c右移16位得到共享锁的状态位
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
// 将状态位c与低十六位1取与得到独占锁的状态位
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
ReadLock读锁的实现:
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 争夺读锁
public void lock() {
sync.acquireShared(1);
}
// 以共享锁的模式竞争锁,线程被中断时抛出异常
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 尝试争夺锁
public boolean tryLock() {
return sync.tryReadLock();
}
// 尝试在有限时间内争夺锁
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
// 尝试释放此锁定
public void unlock() {
sync.releaseShared(1);
}
// ReadLocks是共享锁,不需要支持Condition条件队列,因此调用会直接抛出异常
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
WriteLock写锁的实现:
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
// 争夺读锁
public void lock() {
sync.acquire(1);
}
// 竞争锁,线程被中断时抛出异常
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// 尝试获取写锁
public boolean tryLock( ) {
return sync.tryWriteLock();
}
// 在规定时间内获取锁
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// 释放锁
public void unlock() {
sync.release(1);
}
// 得到等待队列Condition
public Condition newCondition() {
return sync.newCondition();
}
public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
// 查询当前线程是否持有此写锁定
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
// 返回当前线程持有的锁数量
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
上面两段代码对比之后其实差别不大,而读锁的实现和ReentrantLock里面的实现几乎相同,都是使用了AQS的acquire/release
操作。
首先我们先看看我们的老朋友:
// 争夺锁
public void lock() {
sync.acquire(1);
}
经过这么久的学习大家应该已经了解这东西的套路了吧,没错,这个肯定是调用了AQS中的acquire(Int)
方法,在其中先尝试获取锁,如果获取失败就将当前线程封装为结点并且挂起,而尝试获取锁的方法由当前类实现,结点的管理就归功于AQS的CLH队列啦。几乎所有争夺锁的操作都千篇一律是这样的。不过相对于ReentrantLock锁来说,这里的实现不是看fairSync还是noFairSync,而是Sync。
这里贴一段熟悉的代码:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这里说一下为什么对与tryAcquire(Int)
要分fairSync跟noFairSync的实现,其实差别只有一个:那就是fairSync在使用CAS竞争锁之前会使用!hasQueuedPredecessors()
来判断下当前线程是否在AQS头部。没错。。你没看错。。tryAcquire(int acquires)
跟nonfairTryAcquire(int acquires)
两段那么长的代码差别只有一个。。
而在ReentrantReadWriteLock中它的实现就简洁很多了,下面让我们看一下:
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;
}
// 是否需要阻塞(公平锁与非公平锁有不同的实现)
// 不需要则尝试获得锁
// 一般获取失败在外部会有循环CAS获取
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 设置获取锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
相比于ReentrantLock的实现,主要多加了一个对读锁的判断,在读锁上锁的情况下,写锁是不能够进入的。除此之外的一个不同点就是writerShouldBlock()
,这个看意思大概是判断是否需要堵塞。
看下ReentrantLock的两个实现:
// 公平锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
// 非公平锁
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
这样大家应该猜到了吧,这个writerShouldBlock()
就是用来区分是否需要使用!hasQueuedPredecessors()
方法的,让我们看下他怎么实现的:
在Sync内部类中:
abstract boolean writerShouldBlock();
将其定义为一个抽象方法,在fairSync跟noFairSync子类中重写:
// 公平锁
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
// 非公平锁
final boolean writerShouldBlock() {
return false; // 总是无需堵塞
}
有没有感觉设计得十分巧妙。。反正我是这么觉得的。
继续看下去之前先猜猜他是怎么实现的。这个方法跟lock(Int)
方法有何区别?
下面公布答案:
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
同理,只有一点区别:一开始会检查线程是否被中断,而且doAcquireInterruptibly(arg)
里面已经包含有了addWaiter(Node.EXCLUSIVE)
方法,而里面的实现与acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
的区别则是,前者当当前结点的线程被中断时,会抛出中断异常;而后者会在获取锁成功后真正中断该线程,此时不会抛出异常。
// 除了缺少对writerShouldBlock的调用之外,这与tryAcquire的效果相同。
final boolean tryWriteLock() {
Thread current = Thread.currentThread();
int c = getState();
if (c != 0) {
int w = exclusiveCount(c);
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
}
if (!compareAndSetState(c, c + 1))
return false;
setExclusiveOwnerThread(current);
return true;
}
仅仅尝试一次获取锁的操作,不能保证一定能够获取到锁。除了缺少对writerShouldBlock的调用之外,这与tryAcquire的效果相同。
下面还有一个加了时间限定的实现:
// 在规定时间内获取锁
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
还是熟悉的配方还是熟悉的味道。。又是AQS的一个方法,重写tryAcquire(Int)
就完事啦~注意这里直接是中断则抛出异常的,记得catch。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
// 释放锁
public void unlock() {
sync.release(1);
}
还是AQS中的方法,主要是重写了tryRelease(Int)方法,AQS内部实现了CHL队列:
// 释放持有的锁,用于实现unlock操作
public final boolean release(int arg) {
if (tryRelease(arg)) {
// 成功释放锁,即已将锁的state状态位置为0
// 看AQS队列中的头结点是否为空并且能否被唤醒,如果可以的话就唤醒继任节点
Node h = head;
if (h != null && h.waitStatus != 0)
// 唤醒头结点的后继结点
unparkSuccessor(h);
return true;
}
return false;
}
重点是tryRelease(arg)
对于释放锁条件的判断以及后续的操作:
protected final boolean tryRelease(int releases) {
// 不是当前占有锁的线程不能够释放
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
// 判断重入锁是否已清空
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
与ReentrantLock类相比只多了个对状态位的位操作,具体操作都是减少指定的重入锁的数量。
// 得到等待队列Condition
public Condition newCondition() {
return sync.newCondition();
}
// 查询当前线程是否持有此写锁定
public boolean isHeldByCurrentThread() {
return sync.isHeldExclusively();
}
// 返回当前线程持有的锁数量
public int getHoldCount() {
return sync.getWriteHoldCount();
}
看完写锁操作再来看读锁操作会简单很多,在一些地方上可能会跳过,如果有看不懂的地方可以查阅一下之前的文章,都是会有说明的。
在看读锁操作之前我们要先看一下HoldCounter这个类:
// 记录当前线程持有共享锁的数量,这个数量必须要与线程绑定在一起,否则操作其他线程锁就会抛出异常。
// 每线程读取保持计数的计数器。维持为ThreadLocal;缓存在cachedHoldCounter中
static final class HoldCounter {
int count = 0; // 计数器
final long tid = getThreadId(Thread.currentThread()); // 线程编号
}
在这里存储了一个计数器count
跟一个线程编号tid
,该类的用处是记录当前线程持有共享锁的数量,通过该类可以实现读锁即共享锁的可重入。但是我们根据这两个数据无法将HoldCounter 与线程绑定起来,因此JUC使用了一个ThreadLocal将该类与线程绑定起来。具体代码如下:
ThreadLocalHoldCounter 是 Sync 的内部静态类:
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
@Override
public HoldCounter initialValue() {
return new HoldCounter();
}
}
此时我们便通过 ThreadLocalHoldCounter 类,将HoldCounter 与线程绑定起来。此时我们使用 HoldCounter 持有的线程编号,这样在释放锁的时候就能知道 ReadWriteLock 里面缓存的上一个读取线程(cachedHoldCounter
)是否是当前线程。这样做的好处是可以减少ThreadLocal.get()
方法的次调用数,因为这也是一个耗时操作。
需要说明的是这样HoldCounter 绑定线程编号而不绑定线程对象的原因是,避免 HoldCounter 和 ThreadLocal 互相绑定而导致 GC 难以释放它们(尽管 GC 能够智能的发现这种引用而回收它们,但是这需要一定的代价)。
下面说一些会涉及到HoldCounter的字段或方法,方便下面写锁的阅读:
// 当前线程的holdCounter
private transient ThreadLocalHoldCounter readHolds;
// 最后一个获得读锁的线程的 HoldCounter 的缓存对象
private transient HoldCounter cachedHoldCounter;
// 第一个获取读锁的线程
private transient Thread firstReader = null;
// 第一个获取读锁的重入数
private transient int firstReaderHoldCount;
// 这里的构造函数会给readHolds即当前线程的holdCounter初始化,因此下面会有直接调用的场景
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
// 争夺读锁
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
看吧,还是只有tryAcquireShared(arg)
是被重写的。
不过此处的共享锁可跟咋们之前学习到的共享锁有所不同,此处共享锁支持了可重入,咋们来看下具体是怎么实现的吧:
protected final int tryAcquireShared(int unused) {
// 得到当前线程
Thread current = Thread.currentThread();
// 得到状态为
int c = getState();
// 如果写锁已经被获取并且获取写锁的线程不是当前线程的话,当前线程获取读锁失败返回-1
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
// 得到共享锁的状态位
int r = sharedCount(c);
// 检查持锁线程head后续节点s是否为写锁,若真则返回true
// 在不超过共享锁数量的前提下更新共享锁数量
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
// 此处已是修改状态位成功
// 若之前共享锁的状态位为0,即没有线程占有锁,则将当前线程设置为firstReader
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
// 如果获取读锁的线程为第一次获取读锁的线程,则重入数+1
firstReaderHoldCount++;
} else {
// 拿到当前线程的holdCount
HoldCounter rh = cachedHoldCounter;
// 判断holdCount是否为空或是否正确
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
// 增加当前线程的共享锁重入数量
rh.count++;
}
return 1;
}
// 处理CAS操作失败的自旋,已经实现重入性
return fullTryAcquireShared(current);
}
具体步骤如下:
fullTryAcquireShared(current);
处理CAS操作失败的自旋。按照写锁的模式来看下公平锁与非公平锁对于readerShouldBlock()
的实现:
非公平锁:
// 非公平锁,若当前占有锁为写锁,则应该堵塞该线程
final boolean readerShouldBlock() {
// 检查持锁线程head后续节点s是否为写锁,若真则返回true
return apparentlyFirstQueuedIsExclusive();
}
// 检查持锁线程head后续节点s是否为写锁,若真则返回true
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null && // head非空
(s = h.next) != null && // 后继节点非空
!s.isShared() && // 后继节点不是共享线程
s.thread != null; // 后继节点线程非空
}
公平锁:
// 判断在AQS的CLH队列中当前线程前方是否有其他节点,若有则应该堵塞该线程
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
fullTryAcquireShared(current);
这个方法与上面方法的相似度很高,增加了对需要阻塞时对于重入锁的判断,是前者自旋重试的逻辑。
// 读取的完整版本,处理CAS未命中和tryAcquireShared中未处理的重入读取。
final int fullTryAcquireShared(Thread current) {
// 此代码与tryAcquireShared中的代码部分冗余,但总体上更简单,因为不会使tryAcquireShared与重试和延迟读取保持计数之间的交互变得复杂。
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;
if (rh == null || rh.tid != getThreadId(current)) {
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove(); // 计数为 0 ,说明没得到读锁,清空线程变量
}
}
// 说明没得到读锁
if (rh.count == 0)
return -1;
}
}
// 读锁超出最大范围
if (sharedCount(c) == MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// CAS设置读锁成功
// 此处跟tryAcquireShared(Int)内的实现差不多一样
if (compareAndSetState(c, c + SHARED_UNIT)) {
// 如果是第一次获取读锁,则更新firstReader和firstReaderHoldCount
if (sharedCount(c) == 0) {
firstReader = current;
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;
}
}
}
同上面写锁的操作差不多,主要是重写了tryAcquireShared(arg)
方法:
// 以共享锁的模式竞争锁,线程被中断时抛出异常
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
// 以共享锁的模式竞争锁,线程被中断时抛出异常
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 尝试竞争共享锁,返回-1时即竞争失败
if (tryAcquireShared(arg) < 0)
// 以共享可中断模式获取。循环CAS直到成功获取或线程被中断
doAcquireSharedInterruptibly(arg);
}
具体可以参考写锁跟以前有关共享锁的文章,比如CountDownLock的操作。
// 尝试争夺锁
public boolean tryLock() {
return sync.tryReadLock();
}
// 除了缺少对readerShouldBlock的调用之外,这与tryAcquireShared的效果相同。
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;
}
}
}
不多说,贴出来,看过tryAcquireShared()
的可以跳过。
// 尝试在有限时间内争夺锁
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
又到了重点戏了:
// 尝试释放此锁定
public void unlock() {
sync.releaseShared(1);
}
// 以共享模式发布。如果tryReleaseShared返回true,则通过解除阻塞一个或多个线程来实现。
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
大家伙学到这里应该知道了。。重写tryReleaseShared(arg)
就完事啦,对于CLH的操作就留给AQS实现吧。
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
// 如果想要释放锁的线程为第一个获取锁的线程
if (firstReader == current) {
// 若之前仅获取了一次,则需要将firstReader 设置null
// 如果该锁之前有重入的,则firstReaderHoldCount - 1
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
// 获取rh对象
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))
// 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;
}
}
看到这里大家应该也注意到有个奇怪的参数混入其中了,unused
。没错。。顾名思义,它真的没有被用到。
// ReadLocks是共享锁,不需要支持Condition条件队列,因此调用会直接抛出异常
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
这次的ReentrantReadWriterLock的解析到这里就先告一段落了,咋们就来进行一段小小的总结了。
从这个类的学习中大家应该会感受到AQS设计的美妙之处了,将CLH队列的维护统统交给AQS来实现,而子类呢,则负责实现对于获取锁以及释放锁操作的判断就好了,十分方便。
ReentrantReadWriterLock涉及到了两种锁,写锁与读锁,也可以称之为独占锁与共享锁,不过此处的共享锁利用了内部的实现类HoldCounter对重入锁的数量进行计数以及ThreadLocal将前者与线程绑定起来。也算是另辟蹊径了。
读写锁的主要特性: