Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to realize AQS sharing Mode and concurrent tool Class in Java

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/02 Report--

This article mainly shows you "how to achieve AQS sharing mode and concurrent tool class in Java", the content is easy to understand, clear, hope to help you solve your doubts, the following let Xiaobian lead you to study and learn "how to achieve AQS sharing mode and concurrent tool class in Java" this article.

Use the example

The following example is very useful. I am a porter for javadoc:

/ / this is a story about caching operations: class CachedData {Object data; volatile boolean cacheValid; / / read-write lock instance final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock (); void processCachedData () {/ / get read lock rwl.readLock () .lock () If (! cacheValid) {/ / if the cache expires, or release the read lock for null / /, and then acquire the write lock (as you will see later, if you acquire the write lock without releasing the read lock, a deadlock condition will occur) rwl.readLock () .unlock (); rwl.writeLock () .lock () Try {if (! cacheValid) {/ / rejudge, because another writer thread may have executed data = while waiting for the write lock. CacheValid = true;} / / acquire a read lock (if you hold a write lock, you are allowed to acquire a read lock, which is called "lock degradation", and vice versa). Rwl.readLock () .lock ();} finally {/ / release the write lock, leaving a read lock rwl.writeLock () .unlock (); / / Unlock write, still hold read}} try {use (data) } finally {/ / release read lock rwl.readLock () .unlock ();}

ReentrantReadWriteLock is divided into two instances: read lock and write lock. Read lock is a shared lock, which can be used by multiple threads at the same time, and write lock is an exclusive lock. The thread holding the write lock can continue to acquire the read lock and vice versa.

Overview of ReentrantReadWriteLock

This section is more important, we should first look at the big framework of ReentrantReadWriteLock, and then go to the source details.

First, let's take a look at the structure of ReentrantReadWriteLock, which has a number of nested classes:

Let's take a closer look at the information in this picture. Then let's bring up the code of ReadLock and WriteLock together to make it clearer:

It is clear that the methods in both ReadLock and WriteLock are implemented through the class Sync. Sync is a subclass of AQS, and then derives fair and unfair patterns.

From the Sync methods they call, we can see that ReadLock uses shared mode and WriteLock uses exclusive mode.

Wait, how can the same AQS instance use both shared mode and exclusive mode?

Here's a review of AQS. Let's compare the sharing mode and exclusive mode of AQS horizontally:

The essence of AQS lies in the internal attribute state:

For exclusive mode, 0 usually indicates that the lock can be acquired, 1 indicates that the lock has been acquired by someone else, and reentrant exception

In shared mode, each thread can add or subtract state.

That is to say, exclusive mode and shared mode operate completely differently for state, so how do you use state in read-write lock ReentrantReadWriteLock? The answer is to divide the 32-bit int value of state into high 16 bits and low 16 bits for shared mode and exclusive mode, respectively.

Source code analysis

With the previous concept, we should know it in mind, the following is no longer so verbose, direct code analysis.

It is not difficult to add 1500 lines of comments to the source code, and the amount of code we want to see is small. If you have understood all the previous sections, it is relatively simple to just start from the beginning and look down.

The first few lines of ReentrantReadWriteLock are simple. Let's slide down to the Sync class and take a look at all its properties:

Abstract static class Sync extends AbstractQueuedSynchronizer {/ / the following block divides state into two, with high 16 bits for shared mode and low 16 bits for exclusive mode static final int SHARED_SHIFT = 16; static final int SHARED_UNIT = (1 SHARED_SHIFT;} / / take the low 16-bit value of c, representing the number of reentrants of the write lock, because the write lock is the exclusive mode static int exclusiveCount (int c) {return c & EXCLUSIVE_MASK) } / / an instance of this nested class is used to record the number of read locks held by each thread (read lock reentry) static final class HoldCounter {/ / the number of read locks held by int count = 0; / / thread id final long tid = getThreadId (Thread.currentThread ()) } / / the subclass static final class ThreadLocalHoldCounter extends ThreadLocal {public HoldCounter initialValue () {return new HoldCounter ();}} / * combines the above two classes and uses a ThreadLocal to record the number of read locks held by the current thread * / private transient ThreadLocalHoldCounter readHolds / / used for caching to record the number of read lock reentrants of "the last thread who acquired the read lock". / / so no matter which thread acquires the read lock, it takes this value as its own, so it does not have to query map in ThreadLocal. / / it is not a theoretical basis: usually the acquisition of the read lock is quickly accompanied by the release of the read lock. / / obviously, during the period of acquisition-> release of the read lock This cache can help improve performance private transient HoldCounter cachedHoldCounter if no other thread acquires the read lock / / the first thread to acquire the read lock (and it did not release the read lock), and the number of read locks it holds private transient Thread firstReader = null; private transient int firstReaderHoldCount; Sync () {/ / initialize the ThreadLocal attribute readHolds readHolds = new ThreadLocalHoldCounter (); / / to ensure the memory visibility of readHolds setState (getState ()); / / ensures visibility of readHolds}.}

The high 16 bits of state represent the number of times the read lock is acquired, including the number of reentrants. The read lock is acquired by adding 1 at a time, and releasing the read lock once minus 1.

The lower 16 bits of state represent the number of times the write lock is acquired. Because the write lock is an exclusive lock and can only be acquired by one thread, it represents the number of reentrants.

Each thread needs to maintain its own HoldCounter and record the number of read locks acquired by the thread, so that it can know whether it is a read lock reentry or not, and maintain it with the ThreadLocal attribute readHolds

What's the use of cachedHoldCounter? It's not really useful, but it can suggest performance. Caching the HoldCounter of the thread that last acquired the read lock here is better than using ThreadLocal, because ThreadLocal is queried internally based on map. But cachedHoldCounter can only cache one thread after all, so the reason for it to improve performance is that the acquisition of the read lock is usually followed by the release of the read lock. I may not express it very well here, but you should understand it.

What are firstReader and firstReaderHoldCount for? It's not really useful, but it can also suggest performance. Record the "first" thread that acquires the read lock in the firstReader property, the first one here is not a global concept, and when the thread currently represented by the firstReader releases the read lock, later threads will occupy this property. FirstReader and firstReaderHoldCount make it very convenient and fast to record the number of read lock reentrants when there is no read lock competition

If a thread uses firstReader, it does not need to take up cachedHoldCounter

Personally, I think that the biggest headaches for beginners in the read-write lock source code are these attributes that are used to improve performance, so that everyone can see them in the clouds. This is mainly because the ThreadLocal is operated internally through a ThreadLocalMap, which increases the retrieval time. In many scenarios, the thread executing unlock is often the thread that just executed lock for the last time, and there may be no other thread to lock. There are also many scenarios where reading lock competition is not likely to occur.

The above said so much, I hope to help you reduce the pressure of reading the source code, you can also look at the back first, and then slowly experience.

In front of us, we all seem to only talk about read locks, not write locks at all, mainly because write locks are really much simpler. I also specially put the source code of write locks in the back, so let's gnaw off the most difficult read locks first.

Read lock acquisition

I'm not going to say one line in source order, let's talk about it in terms of use.

Let's take a look at the lock process of the read lock ReadLock:

/ / ReadLockpublic void lock () {sync.acquireShared (1);} / AQSpublic final void acquireShared (int arg) {if (tryAcquireShared (arg))

< 0) doAcquireShared(arg);} 然后我们就会进到 Sync 类的 tryAcquireShared 方法: 在 AQS 中,如果 tryAcquireShared(arg) 方法返回值小于 0 代表没有获取到共享锁(读锁),大于 0 代表获取到 回顾 AQS 共享模式:tryAcquireShared 方法不仅仅在 acquireShared 的最开始被使用,这里是 try,也就可能会失败,如果失败的话,执行后面的 doAcquireShared,进入到阻塞队列,然后等待前驱节点唤醒。唤醒以后,还是会调用 tryAcquireShared 进行获取共享锁的。当然,唤醒以后再 try 是很容易获得锁的,因为这个节点已经排了很久的队了,组织是会照顾它的。 所以,你在看下面这段代码的时候,要想象到两种获取读锁的场景,一种是新来的,一种是排队排到它的。 protected final int tryAcquireShared(int unused) { Thread current = Thread.currentThread(); int c = getState(); // exclusiveCount(c) 不等于 0,说明有线程持有写锁, // 而且不是当前线程持有写锁,那么当前线程获取读锁失败 // (另,如果持有写锁的是当前线程,是可以继续获取读锁的) if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current) return -1; // 读锁的获取次数 int r = sharedCount(c); // 读锁获取是否需要被阻塞,稍后细说。为了进去下面的分支,假设这里不阻塞就好了 if (!readerShouldBlock() && // 判断是否会溢出 (2^16-1,没那么容易溢出的) r < MAX_COUNT && // 下面这行 CAS 是将 state 属性的高 16 位加 1,低 16 位不变,如果成功就代表获取到了读锁 compareAndSetState(c, c + SHARED_UNIT)) { // ======================= // 进到这里就是获取到了读锁 // ======================= if (r == 0) { // r == 0 说明此线程是第一个获取读锁的,或者说在它前面获取读锁的都走光光了,它也算是第一个吧 // 记录 firstReader 为当前线程,及其持有的读锁数量:1 firstReader = current; firstReaderHoldCount = 1; } else if (firstReader == current) { // 进来这里,说明是 firstReader 重入获取读锁(这非常简单,count 加 1 结束) firstReaderHoldCount++; } else { // 前面我们说了 cachedHoldCounter 用于缓存最后一个获取读锁的线程 // 如果 cachedHoldCounter 缓存的不是当前线程,设置为缓存当前线程的 HoldCounter HoldCounter rh = cachedHoldCounter; if (rh == null || rh.tid != getThreadId(current)) cachedHoldCounter = rh = readHolds.get(); else if (rh.count == 0) // 到这里,那么就是 cachedHoldCounter 缓存的是当前线程,但是 count 为 0, // 大家可以思考一下:这里为什么要 set ThreadLocal 呢?(当然,答案肯定不在这块代码中) // 既然 cachedHoldCounter 缓存的是当前线程, // 当前线程肯定调用过 readHolds.get() 进行初始化 ThreadLocal readHolds.set(rh); // count 加 1 rh.count++; } // return 大于 0 的数,代表获取到了共享锁 return 1; } // 往下看 return fullTryAcquireShared(current);} 上面的代码中,要进入 if 分支,需要满足:readerShouldBlock() 返回 false,并且 CAS 要成功(我们先不要纠结 MAX_COUNT 溢出)。 那我们反向推,怎么样进入到最后的 fullTryAcquireShared: readerShouldBlock() 返回 true,2 种情况: 在 FairSync 中说的是 hasQueuedPredecessors(),即阻塞队列中有其他元素在等待锁。 也就是说,公平模式下,有人在排队呢,你新来的不能直接获取锁 在 NonFairSync 中说的是 apparentlyFirstQueuedIsExclusive(),即判断阻塞队列中 head 的第一个后继节点是否是来获取写锁的,如果是的话,让这个写锁先来,避免写锁饥饿。 作者给写锁定义了更高的优先级,所以如果碰上获取写锁的线程马上就要获取到锁了,获取读锁的线程不应该和它抢。 如果 head.next 不是来获取写锁的,那么可以随便抢,因为是非公平模式,大家比比 CAS 速度 compareAndSetState(c, c + SHARED_UNIT) 这里 CAS 失败,存在竞争。可能是和另一个读锁获取竞争,当然也可能是和另一个写锁获取操作竞争。 然后就会来到 fullTryAcquireShared 中再次尝试: /** * 1\. 刚刚我们说了可能是因为 CAS 失败,如果就此返回,那么就要进入到阻塞队列了, * 想想有点不甘心,因为都已经满足了 !readerShouldBlock(),也就是说本来可以不用到阻塞队列的, * 所以进到这个方法其实是增加 CAS 成功的机会 * 2\. 在 NonFairSync 情况下,虽然 head.next 是获取写锁的,我知道它等待很久了,我没想和它抢, * 可是如果我是来重入读锁的,那么只能表示对不起了 */final int fullTryAcquireShared(Thread current) { HoldCounter rh = null; // 别忘了这外层有个 for 循环 for (;;) { int c = getState(); // 如果其他线程持有了写锁,自然这次是获取不到读锁了,乖乖到阻塞队列排队吧 if (exclusiveCount(c) != 0) { if (getExclusiveOwnerThread() != current) return -1; // else we hold the exclusive lock; blocking here // would cause deadlock. } else if (readerShouldBlock()) { /** * 进来这里,说明: * 1\. exclusiveCount(c) == 0:写锁没有被占用 * 2\. readerShouldBlock() 为 true,说明阻塞队列中有其他线程在等待 * * 既然 should block,那进来这里是干什么的呢? * 答案:是进来处理读锁重入的! * */ // firstReader 线程重入读锁,直接到下面的 CAS if (firstReader == current) { // assert firstReaderHoldCount >

0;} else {if (rh = = null) {rh = cachedHoldCounter If (rh = = null | | rh.tid! = getThreadId (current)) {/ / cachedHoldCounter is not cached by the current thread / / then go to ThreadLocal to get the current thread's HoldCounter / / if the current thread has never initialized the value in ThreadLocal Get () initializes rh = readHolds.get () / / if you find that count = = 0, that is, it is initialized by the previous line of code, then execute remove / / then go down two or three lines and queue up to if (rh.count = = 0) readHolds.remove () }} if (rh.count = = 0) / / queue to go. Return-1;} / * this code took me a long time to figure out what it does. All I need to know is that it deals with reentrants. * just to ensure that the read lock reentrant operation is successful, instead of being stuffed into the blocking queue to wait * * another message is the handling of the ThreadLocal variable readHolds: * if count = = 0 is found after get (), the remove () operation will be done. * this line of code is helpful for understanding other code * /} if (sharedCount (c) = = MAX_COUNT) throw new Error ("Maximum lock count exceeded") If (compareAndSetState (c, c + SHARED_UNIT)) {/ / if the CAS is successful, it means that the read lock has been successfully acquired. / / the next thing you need to do is to set firstReader or cachedHoldCounter if (sharedCount (c) = = 0) {/ / if you find that sharedCount (c) equals 0, set the current thread to firstReader firstReader = current FirstReaderHoldCount = 1;} else if (firstReader = = current) {firstReaderHoldCount++;} else {/ / the following lines set cachedHoldCounter to the current thread 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 } / / returns a number greater than 0, which means that the read lock return 1;} has been obtained.

FirstReader is the thread that changes the number of lock acquisition times from 0 to 1 each time.

If you can cache it in firstReader, do not cache it in cachedHoldCounter.

The above source code analysis should be very detailed, if you can not quite understand some of the comments above, then you can look back first, and then read it a few more times.

Read lock release

Let's take a look at the process of reading lock release:

/ / ReadLockpublic void unlock () {sync.releaseShared (1);} / / Syncpublic final boolean releaseShared (int arg) {if (tryReleaseShared (arg)) {doReleaseShared (); / / this code actually wakes up the thread that acquired the lock, and you'll see return true;} return false;} / / Syncprotected final boolean tryReleaseShared (int unused) {Thread current = Thread.currentThread () If (firstReader = = current) {if (firstReaderHoldCount = = 1) / / if it is equal to 1, then the lock will no longer be held after the lock is unlocked. Set firstReader to null for later threads / / Why not set firstReaderHoldCount = 0 by the way? Because it is not necessary, other threads will set the value firstReader = null; else firstReaderHoldCount--;} else {/ / to determine whether the cachedHoldCounter is cached by the current thread. If not, go to ThreadLocal to get HoldCounter rh = cachedHoldCounter; if (rh = = null | | rh.tid! = getThreadId (current)) rh = readHolds.get () Int count = rh.count; if (count

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report