Before we understand the J.U.C principle and the locking mechanism, we introduce the core and most complex basic class of the J.U.C framework:Java.util.concurrent.locks.AbstractQueuedSynchronizer.
AQS
Abstractqueuedsynchronizer, referred to as AQS, is the most complex class of j.u.c, leading to the vast majority of lectures on concurrency principles or actual combat will not mention this class. But an open-minded author is willing to use his limited ability and energy to explore one or two (some of the resources have also been analyzed by some authors.) )。
First from the theoretical knowledge, after understanding the relevant principles will be based on some analysis of the source code, and finally add some actual combat to describe.
In the inheritance system above, Abstractqueuedsynchronizer is countdownlatch/futuretask/reentrantlock/renntrantreadwritelock/ Semaphore, so Abstractqueuedsynchronizer is the premise of lock/executor implementation. Fair lock, unfair lock, Condition, Countdownlatch, Semaphore and so on in the back of the space to explain.
The complete design principle can be referenced in Doug Lea's paper, the Java. util. concurrent Synchronizer Framework , here do some brief analysis.
The basic idea is to represent a Synchronizer that supports the following two operations:
Get Lock: First determine whether the current state is allowed to acquire the lock, if it is to obtain a lock, otherwise blocking the operation or get failed, that is, if an exclusive lock can block, if it is a shared lock may fail. In addition, if the thread is blocked, the thread needs to go into the blocking queue. When the status bit allows the lock to be acquired, the state is modified and removed from the queue if the queue is entered.
While (synchronization state does not allow acquire) {
Enqueue current thread if not already queued;
possibly block current thread;
}
Dequeue current thread if it is queued;
Release Lock: This process modifies the status bit and wakes one or more threads in the queue if the thread is blocked because of a state bit.
Update synchronization State;
if (State could permit a blocked thread to acquire)
Unlock one or more queued threads;
To support the above two actions, you must have the following conditions:
- Status bits of the atomic manipulation Synchronizer
- Blocking and waking Threads
- An orderly queue
The goal is clear, the problem to solve is clear, then the rest is to solve the above three problems.
Atomic operation of the state bit
Here, a 32-bit integer is used to describe the state bit, and the theoretical knowledge of the atomic operation in the previous chapter is useful, and the CAS operation is still used here to solve the problem. In fact there is also a 64-bit version of the Synchronizer (Abstractqueuedlongsynchronizer), this is not discussed here.
Blocking and waking Threads
The standard Java API is not able to suspend (block) a thread and then wake it up at some point in the future. The JDK 1.0 API has Thread.Suspend and Thread.Resume, and it continues. But these are outdated APIs and are not recommended.
This feature was implemented in the Locksupport class using JNI after JDK 5.0.
Locksupport.park ()
Locksupport.park (Object)
Locksupport.parknanos (Object, long)
Locksupport.parknanos (Long)
Locksupport.parkuntil (Object, long)
Locksupport.parkuntil (Long)
Locksupport.unpark (Thread)
In the above API, Park () is called in the current thread, causing the thread to block, and the parameter object is a suspended object, so that the monitor can know what resource the thread is blocking. Because Park () returns immediately, it is often necessary to detect competing resources in a loop to determine whether to make the next block. Park () returned for three reasons:
- One of the other threads calls the current thread as the target
unpark
;
- One of the other threads interrupts the current thread;
- The call is returned logically (that is, without justification).
In fact, the third rule determines the need for loop detection, similar to the normally written while (Checkcondition ()) {Thread.Sleep (time);} A similar function.
Ordered queues
Use the CHL list in Aqs to solve the problem of ordered queues.
The CHL model adopted by AQS uses the following algorithm to complete the FIFO queue and out queue process.
For in-queue (enqueue): with CAS operations, each time the tail node is compared to the same, then inserted into the tail node.
do {
pred = tail;
}while (!compareandset (Pred,tail,node));
For out-of-queue (dequeue): Because each node also caches a state and decides whether to queue out, a spin wait is required when the condition is not met, and the head node is set to the next node once the condition is met.
while (Pred.status! = Released);
Head = node;
In fact, the spin wait here is also implemented using Locksupport.park ().
There are three core fields in the Aqs:
private volatile int state;
Private transient volatile Node head;
Private transient volatile Node tail;
Where state describes how many threads have acquired a lock, state<=1 for a mutex. The Head/tail plus CAS operation constitutes a CHL FIFO queue. The following are the properties of the node nodes.
volatile int waitstatus; The wait state of a node, a node may be in the following states:
- CANCELLED = 1: The node operation is interrupt because of a timeout or the corresponding thread. The node should not remain in this state, and will be kicked out of the CHL queue once this state is reached.
- SIGNAL = 1: The successor node of the node is (or will become) the blocked state (for example, through the Locksupport.park () operation), so that once a node is released (unlocked) or canceled it needs to wake up (Locksupport.unpack () ) its successor node.
- CONDITION =-2: Indicates that the thread corresponding to the node is blocked because it does not meet a condition (CONDITION).
- 0: Normal state, the new non-condition node is this state.
- A non-negative identity node does not need to be notified (wakeup).
volatile Node prev; The previous node of this node. The waitstatus of a node depends on the state of the previous node.
volatile Node next; The second node of this node. Whether the latter node is awakened (Uppark ()) depends on whether the current node is disposed.
volatile thread thread; The thread that the node binds to.
Node Nextwaiter; The next wait condition (Condition) node, since Condition is exclusive, there is a simple queue to describe the thread node on the Condition.
AQS in J.U.C is a very central tool, but also very complex, which takes into account a lot of logical implementation, so in later chapters always try to introduce the characteristics and implementation of AQS.
This section mainly introduces some theoretical backgrounds and related data structures, and in the next section we will look at how Lock.lock/unlock is implemented based on the above knowledge.
Resources:
(1) Reentrantlock Code Anatomy of Reentrantlock.lock Reentrantlock code anatomy reentrantlock.unlock Reentrantlock Code Anatomy of reentrantlock.lockinterruptibly
(2) Java Multithreaded--java.util.concurrent.locks.abstractqueuedsynchronizer parsing (contains only multithreaded synchronization examples)
(3) Handling interruptedexception
(4) Abstractqueuedsynchronizer source parsing Reentrantlock (a) Abstractqueuedsynchronizer source parsing Reentrantlock (ii)
(5) TheJava. util. Concurrent Synchronizer Framework
In layman's Java Concurrency (7): Lock mechanism Part 2 aqs[turn]