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

Example Analysis of AQS principle in Java concurrency

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

Share

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

This article shares with you the content of an example analysis of the principle of AQS in Java concurrency. The editor thinks it is very practical, so share it with you as a reference and follow the editor to have a look.

1. Thread blocking primitive

Thread blocking and awakening of Java is done through the park and unpark methods of the Unsafe class.

Both of these methods are native methods, and they are the core functions implemented by the C language. Park means to stop and let the currently running thread Thread.currentThread () sleep, and unpark means to release the stop and wake up the specified thread.

At the bottom, these two methods are implemented using the semaphore mechanism provided by the operating system. The specific implementation process should be in-depth study of the C code, which will not be analyzed here for the time being. The two parameters of the park method are used to control how long the sleep lasts. The first parameter, isAbsolute, indicates whether the second parameter is absolute or relative, in milliseconds.

The thread runs from startup, and except for the operating system's task scheduling policy, it pauses only when park is called. The secret that a lock can pause a thread is that the lock calls the park method at the bottom.

2 、 parkBlocker

The thread object Thread has an important property, parkBlocker, which holds the park of the current thread for what. It's like there are a lot of cars parked in the parking lot, and these car owners come to take part in an auction and drive away after taking pictures of what they want. So the parkBlocker here probably refers to this "auction". It is the manager coordinator of a series of conflicting threads, and it controls which thread should sleep and which thread should wake up.

This property is set to null when the thread is awakened by unpark. Unsafe.park and unpark do not help us set the parkBlocker property. The utility class responsible for managing this property is LockSupport, which simply wraps the two methods Unsafe.

The lock data structure of Java realizes hibernation and wakeup by calling LockSupport. The value of the parkBlocker field in the thread object is the "queue manager" we will talk about below.

3. Queuing Manager

When multiple threads compete for the same lock, there must be a queuing mechanism to string together those threads that fail to get the lock. When the lock is released, the lock manager picks a suitable thread to occupy the lock that has just been released.

There is a queue manager inside each lock that maintains a queue of waiting threads. The queue manager in ReentrantLock is AbstractQueuedSynchronizer, and its internal waiting queue is a two-way list structure.

When locking is unsuccessful, the current thread puts itself at the end of the waiting list and then calls LockSupport.park to hibernate itself. When other threads unlock, they take a node from the header of the linked list and call LockSupport.unpark to wake it up.

The AbstractQueuedSynchronizer class is an abstract class, which is the parent class of all lock queue managers. The internal queue managers of various forms of locks in JDK inherit this class. It is the core cornerstone of the Java concurrent world.

For example, queue managers within ReentrantLock, ReadWriteLock, CountDownLatch, Semaphore, and ThreadPoolExecutor are all its subclasses. This abstract class exposes some abstract methods, and each lock needs to be customized to the manager. All the concurrent data structures built into JDK are completed under the protection of these locks, which is the foundation of JDK multi-threaded high-rise buildings.

The lock manager maintains a queue in the form of an ordinary two-way list. This data structure is simple, but it is quite complex to maintain carefully, because it requires careful consideration of multithreading concurrency, and every line of code is written with great care.

The implementer of JDK lock manager is Douglas S. Lea,Java concurrency package, which is almost all written by him alone. In the world of algorithms, the more sophisticated things are, the more suitable for a person to do.

Later, we will write AbstractQueuedSynchronizer as AQS. I must remind readers that AQS is so complex that it's normal to encounter setbacks on the way to understanding it. At present, there is not a book on the market that can easily understand AQS. There are too few people who can understand AQS, and I don't count myself.

4. Fair lock and unfair lock

The fair lock ensures the order in which the lock is requested and acquired. If the lock is free at some point and a thread tries to add the lock, the fair lock must also check to see if any other threads are currently queued. Unfair locks can jump the queue directly. Think of the queue when you buy a hamburger at KFC.

You might ask, if a lock is free, how can it have queued threads? Let's assume that the thread holding the lock has just released the lock, which wakes up the first node thread in the waiting queue, and the awakened thread has just returned from the park method, and then it will try to lock, so the state between park and locking is the free state of the lock, which is very short, and other threads may also be trying to lock during this short period of time.

Secondly, it is important to note that after the thread that executes the Lock.park method sleeps itself, it does not have to wait until other threads unpark to wake up. It may wake up for some unknown reason at any time. Let's look at the source code comments. There are four reasons why park returns:

① other threads unpark the current thread

② time to wake up naturally (park has time parameter)

③ other threads interrupt the current thread

False awakening caused by other unknown reasons in ④

The document does not specify what causes the false wake, but it does indicate that when the park method returns, it does not mean that the lock is free, and that the awakened thread will park itself again after a failed reattempt to acquire the lock. So the locking process needs to be written in a loop, and multiple attempts may be made before successfully getting the lock.

The service efficiency of unfair locks in the computer world is higher than that of fair locks, so Java default locks use unfair locks. However, in the real world, it seems that unfair locks will be less efficient. For example, if you can keep jumping the queue at KFC, you can imagine that the scene must be a mess. The reason why there is a difference between the computer world and the real world is probably because one thread jumping the queue in the computer world does not cause other threads to complain.

5. Shared lock and exclusive lock

ReentrantLock's lock is an exclusive lock, held by one thread, and other threads must wait. The read lock in ReadWriteLock is not an exclusive lock, it allows multiple threads to hold the read lock at the same time, which is a shared lock. Shared locks and exclusive locks are distinguished by the nextWaiter field in the Node class.

So why isn't this field named mode or type or simply called shared? This is because nextWaiter has a different use in other scenarios, just like fields of C language union types, except that the Java language does not have union types.

6. Conditional variables

With regard to conditional variables, the first question to be asked is why conditional variables are needed, and locks are not enough. Consider the following pseudo-code to do something when a condition is met

When the condition is not met, it will loop and retry (other threads will modify the condition by locking), but you need to interval sleep, otherwise the CPU will soar because of idling. There is a problem here, that is, how long sleep is out of control. If the interval is too long, it will slow down the overall efficiency, or even miss the opportunity (the condition is instantly satisfied and then reset immediately), and the interval is too short, which will lead to CPU idling. With conditional variables, this problem can be solved.

The await () method blocks on the cond condition variable until the cond.signal () or cond.signalAll () method is called by another thread, and the lock held by the current thread is automatically released when await () is blocked. When await () is awakened, it will try to hold the lock again (which may need to be queued again), and the await () method will not return until the lock is successfully obtained.

There can be multiple threads blocking on conditional variables, and these blocked threads are concatenated into a conditional waiting queue. When signalAll () is called, all blocked threads are awakened, causing all blocked threads to start scrambling for locks again. If signal () is called, only the thread at the head of the queue will be awakened, which can avoid "group problems".

The await () method must release the lock immediately, otherwise the critical section state cannot be modified by other threads and the result returned by condition_is_true () will not change. This is why the condition variable must be created by the lock object, and the condition variable needs to hold a reference to the lock object so that the lock can be released and re-locked after being woken up by signal.

The lock that creates the condition variable must be an exclusive lock. If the shared lock is released by the await () method, there is no guarantee that the state of the critical section can be modified by other threads.

With conditional variables, the problem that sleep is not easy to control is solved. When the condition is met, the signal () or signalAll () method is called, and the blocked thread can be awakened immediately with almost no delay.

7. Conditional waiting queue

When multiple threads await () are on the same condition variable, a conditional waiting queue is formed. Multiple conditional variables can be created for the same lock, so there will be multiple conditional waiting queues. This queue is similar to AQS's queue structure, except that it is not a two-way queue, but an one-way queue. The nodes in the queue and the nodes in the AQS waiting queue are of the same class, but the node pointer is not prev and next, but nextWaiter.

ConditionObject is the inner class of AQS, in which there will be a hidden pointer this$0 to the external AQS object, and ConditionObject can directly access all properties and methods of the AQS object (locked and unlocked). The waitStatus status of all nodes in the conditional wait queue is marked as CONDITION, indicating that the node is waiting because of the condition variable.

8. Queue transfer

When the signal () method of the conditional variable is called, the head node thread of the conditional waiting queue is awakened, and the node is removed from the conditional waiting queue and transferred to the AQS waiting queue, ready to queue to try to reacquire the lock. At this time, the state of the node changes from CONDITION to SIGNAL, indicating that the current node is awakened and transferred by the condition variable.

The meaning of the nextWaiter field of the transferred node has also changed. It is the pointer to the next node in the conditional queue and whether it is a shared lock or mutex flag in the AQS waiting queue.

9. Read-write lock

Read-write locks are divided into two lock objects, ReadLock and WriteLock, which share the same AQS. The lock count variable state of AQS will be divided into two parts, the first 16bit is the shared lock ReadLock count and the latter 16bit is the mutex WriteLock count. The mutex records the number of reentrants of the current write lock, while the shared lock records the total number of reentrants of all threads currently holding the shared read lock.

Read-write locks also need to consider fair and unfair locks. The fair locking strategy for shared locks and mutexes is the same as ReentrantLock, which is to see if there are any other threads waiting in the queue, and will obediently wait at the end of the queue. The unfair locking strategy is different, and it tends to provide more opportunities for write locks.

If there are any threads of read and write requests in the current AQS queue, the write lock can compete directly, but if the head of the line is a write lock request, then the read lock needs to give up the opportunity to the write lock and queue at the end of the queue. After all, read-write locks are suitable for situations where there are more reads and less writes, and occasional write lock requests should be given a higher priority.

Thank you for reading! This is the end of this article on "example Analysis of AQS principle in Java concurrency". I hope the above content can be helpful to you, so that you can learn more knowledge. if you think the article is good, you can share it out for more people to see!

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