《Java源码分析》:ReadWriteLock(第二部分)
本篇博文主要介绍了ReentrantReadWriteLock内部实现。
ReadWriteLock是一个接口,如下:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*/
Lock readLock();
/**
* Returns the lock used for writing.
*/
Lock writeLock();
}
ReentrantReadWriteLock是ReadWriteLock的实现类。
此类的内部是借助于3个类来完成的。如下
1、Sync。NonFairSync/FairSync。
Sync是继承自AbstractQueuedSynchronizer实现的抽象类,NonFairSync/FairSync都是Sync的子类,分别代表非公平同步器和公平同步器。
2、ReadLock
读锁类,当我们读取数据的时候需要加读锁。
3、WriteLock
写锁类,当我们写数据的时候需要加写锁。
写锁和读锁有一些规则,也是其内部实现的基础。
例如:
1、读锁可以同时被多线程所持有,而写锁只能被一个线程持有且持有的同时不允许其他线程持有读、写锁。
2、可重入性
3、可降级性
4、两个锁所持有的个数由AQS状态位state的高低16来记录。
以上的这些特性都是此类实现的内部基础。
下面我们来分析其内部实现,先冲构造开始。
ReentrantReadWriteLock的构造函数
先看ReentrantReadWriteLock类的构造函数
/**
* Creates a new {@code ReentrantReadWriteLock} with
* default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);//默认创建一个非公平的对象
}
/*
s实例化了AQS的实现类对象,ReadLock、WriteLock对象
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
公平锁类FairSync和非公平类NonFairSync都没有提供构造函数
,因此都会调用父类的无参构造函数
Sync() {
readHolds = new ThreadLocalHoldCounter();
setState(getState()); // ensures visibility of readHolds
}
从上面可以看出,如果我们在使用ReentrantReadWriteLock时,使用如下的ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
新建一个对象,内部是实例化了sync、readerLock、writeLock三个对象。
WriteLock的lock()的内部实现
先来介绍写锁的lock()/unlock()的内部实现。
WriteLock的lock()的实现源码如下:
public void lock() {
sync.acquire(1);
}
/*
此函数的思路为:
1、调用tryAcquire获取独占锁,如果没有获取到,则进行2
2、调用addWaiter函数将节点加入队列中,进行3
3、一直自旋,直到其获得锁
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
/*
* Walkthrough:
* 1. If read count nonzero or write count nonzero
* and owner is a different thread, fail.
* 2. If count would saturate, fail. (This can only
* happen if count is already nonzero.)
* 3. Otherwise, this thread is eligible for lock if
* it is either a reentrant acquire or
* queue policy allows it. If so, update state
* and set owner.
*/
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;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
//NonfairSync类中的写一直不阻塞
final boolean writerShouldBlock() {
return false; // writers can always barge
}
//FairSync类中的
final boolean writerShouldBlock() {
return hasQueuedPredecessors();//队列中是否其它线程的节点
}
写锁的lock()和unlock()的过程可以与独占锁ReentrantLock类似来理解,相信看过ReentrantLock源码后的我们理解这些代码是比较容易的了。
大概思想总结一下:
一个线程想获取写锁,所经历的过程如下
1、由于写锁是独占锁,首先肯定是判断写锁有没有被其它线程拥有,由于写锁不能和读锁共存,因此也要判断读锁有没有被其它线程(也包括自己)拥有。如果以上情况均不符合,则此线程得到锁,立即返回并设置AQS状态位。否则进行2
2、将此线程加入到AQS队列中并进行自旋等待获取锁直至此线程获取到锁。
ReadLock的lock()的内部实现
读锁lock()、unlock()方法的思想可以与共享锁Semaphore、CountDownLatch类似来理解。但是比Semaphore、CountDownLatch稍微要复杂一点。
这是因为ReadLock加锁成功与否还和WriteLock有一定的关系。
下面我们具体来看看
/**
* Acquires the read lock.
*
* <p>Acquires the read lock if the write lock is not held by
* another thread and returns immediately.
*当写锁没有被其它线程持有,则获取一个读锁并立即返回
*如果写锁被其它线程持有,则由于线程调度的原因当前线程被禁用,休眠直至读锁被获取到。
* <p>If the write lock is held by another thread then
* the current thread becomes disabled for thread scheduling
* purposes and lies dormant until the read lock has been acquired.
*/
public void lock() {
sync.acquireShared(1);
}
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
protected final int tryAcquireShared(int unused) {
/*
* Walkthrough:
* 1. If write lock held by another thread, fail.
否则,该线程是有资格获得读锁的,因此根据队列的实际情况此线程是否需要被阻塞,
如果不需要,则获得锁并利用CAS来更新AQS同步队列的状态位。
注意:这一步不检查重入。
* 2. Otherwise, this thread is eligible(有资格的) for
* lock wrt state, so ask if it should block
* because of queue policy. If not, try
* to grant by CASing state and updating count.
* Note that step does not check for reentrant
* acquires, which is postponed to full version
* to avoid having to check hold count in
* the more typical non-reentrant case.
* 3. If step 2 fails either because thread
* apparently not eligible or CAS fails or count
* saturated, chain to version with full retry loop.
*/
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)//写锁被其它的线程占有,则立即返回
return -1;
int r = sharedCount(c);//读锁的个数
/*
如果此线程不应该被阻塞且读锁的个数小于最大锁的限制且CAS设置成功
*/
if (!readerShouldBlock() &&
r < MAX_COUNT &&
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 1;
}
return fullTryAcquireShared(current);
}
上面ReadLock加锁的过程还是比较容易理解的哈。唯一不好理解的地方为:HoldCounter 。这里我自己也懒的去查了。以后就机会我再来看下。
以上就是ReadLock、WriteLock的加锁过程。释放锁的过程就不再介绍了,和其它锁的释放原理类似。
Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。
它的内容包括:
- 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
- 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
- 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
- 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
- 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
- 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
- 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
- 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw
目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:
想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询
同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。