Java and the contract to make a general summary, if there is a mistake, please correct me.
The overall structure of the JUC package is generally as follows
The outer frame is mainly lock (Reentrantlock, Readwritelock, etc.), synchronizer (semaphores, etc.), blocking queues (blockingqueue, etc.), Executor (thread pool), Concurrent Containers (CONCURRENTHASHMAP, etc.), as well as fork/join framework;
The inner layer has the AQS (Abstractqueuedsynchronizer class, the lock function all by him), the non-blocking data structure, the Atomic variable class (Atomicinteger and so on lock-free thread security Class) three kinds.
The underlying implementation is volatile and CAs. The whole contract is actually composed of these two kinds of thought. volatile
A good way to understand the volatile feature is to treat a single read/write of the volatile variable as using the same lock on the
Some individual read/write operations are synchronized. A single read/write operation of a volatile variable, and a read/write operation with a normal variable
are synchronized using the same lock and perform the same effect between them.
Volatile has an attribute.
Visibility. For reading a volatile variable, you can always see (any thread) the last write to this volatile variable
Into.
Atomicity: The read/write of any single volatile variable is atomic, but similar to volatile++ this composite operation does not
has atomic nature.
The memory semantics of volatile read are as follows.
When reading a volatile variable, jmm the thread's corresponding local memory to an invalid. The thread then moves from the primary
Read shared variables in memory.
The memory semantics of volatile write are as follows.
When writing a volatile variable, JMM flushes the shared variable value in the local memory corresponding to the thread to the main
Save.
To implement the memory semantics of volatile, the compiler inserts a memory barrier into the instruction sequence when generating the bytecode
Disables specific types of processor reordering . For the compiler, an optimal placement is found to minimize the insertion barrier's total
Number is almost impossible. To this end, JMM adopted a conservative strategy. The following is a JMM memory barrier insertion strategy based on conservative policies.
• Insert a storestore barrier at the front of each volatile write operation.
• Inserts a storeload barrier behind each volatile write operation.
• Inserts a loadload barrier behind each volatile read operation.
• Inserts a loadstore barrier behind each volatile read operation.
The simple meaning of the above memory barrier is: Storestore barrier, prohibit the above write operation and the following volatile write reorder; Storeload Barrier, prohibit the above write operation and the following volatile read reorder; Loadload barrier, prohibit above reading/ The write operation and the following volatile read operations are reordered; Loadstore Barrier, the above read operation is prohibited and the following volatile write operation reordering.
Since the CAS has both volatile read and volatile write memory semantics, communication between Java threads is now
In the following 4 ways.
1 A thread writes the volatile variable, followed by a B-thread reading the volatile variable.
2 A thread writes volatile variable, then B thread updates this volatile variable with CAs.
3 A thread updates a volatile variable with CAS, followed by a B thread updating the volatile variable with CAs.
4 A thread updates a volatile variable with CAS, followed by a B thread reading the volatile variable.
Summarize the conditions that are appropriate for using the volatile variable (all conditions must be met):
1. Write operations on variables do not depend on the current value of the variable, or you can ensure that only a single thread updates the value of the variable. (Simple is single-threaded write, multithreaded read scene)
2. The variable is not included in the invariant condition with other state variables.
3. You do not need to lock the variable when accessing it. (If you want to lock the words with a normal variable on the line, no need to use volatile) the implementation of the lock
The memory semantics of unlocking and acquiring locks
When the thread releases the lock, JMM flushes the shared variable in the local memory corresponding to the thread into main memory.
When a thread acquires a lock, jmm the thread's corresponding local memory to an invalid. which allows the monitor to be protected by
The critical section code must read the shared variable from main memory.
The summary is:
Thread A releases a lock, essentially a thread A to the next thread that will fetch the lock (thread A
A message that modifies the shared variable.
• Thread B Acquires a lock, essentially that thread B receives a previous thread (before releasing the lock for a total
A message that is modified by the variable.
• Thread A releases the lock, and then thread B acquires the lock, which is essentially a thread A through main memory to thread B
Send messages.
This article uses the Reentrantlock source code to analyze the specific implementation mechanism of the lock memory semantics.
In Reentrantlock, the Lock () method is invoked to get the lock, and the Unlock () method is called to release the lock.
public void Lock () {
sync.lock ();
}
public void Unlock () {
sync.release (1);
}
Its lock is realized by static internal class sync, which is divided into fair lock and unequal lock.
Abstract static class Sync extends Abstractqueuedsynchronizer
static final class Nonfairsync extends Sync //non-fair lock
static final class Fairsync extends Sync //Fair lock
The implementation of the lock relies on the Java Synchronizer Framework Abstractqueuedsynchronizer (AQS), Aqs uses an integral volatile variable named state to maintain the synchronization state. It is the key to reentrantlock memory semantics implementation.
/**
* The synchronization state.
* *
private volatile int state;
Example of the implementation of a non-fair lock
final Void Lock () {//aqs method, using the CAS implementation monitor lock, and if state is 0, obtain the lock and set to 1 if (compareandsetstate (0
, 1)///After acquiring the lock, set the thread that owns the lock as the current thread Setexclusiveownerthread (Thread.CurrentThread ());
else//If the lock has been fetched, join the wait queue acquire (1); } Protected Final Boolean compareandsetstate (int expect, int update) {//below for intrinsics setup to Suppo
RT This return unsafe.compareandswapint (this, stateoffset, expect, update); Public final void acquire (int arg) {if!tryacquire (ARG) && acquirequeued (Addwaiter (NODE.E
Xclusive), Arg) selfinterrupt (); }
The unfair lock writes the volatile variable state at the end of the release lock and reads the volatile variable first when acquiring the lock. Under
Volatile Happens-before rule, the thread that releases the lock is a shared variable that is visible before writing the volatile variable, acquiring the lock
The thread that reads the same volatile variable immediately becomes visible to the thread that acquired the lock.
Compareandsetstate (0, 1) uses CAS operations. The JDK documentation describes the method as follows: If the current state value equals the expected value, the synchronization state is set to the given update value in atomic mode.
You may wonder why this method can do atomic operations, because this operation has volatile read and write memory semantics. is mainly implemented by the Sun.misc.Unsafe class, it is the native method, which is not done in depth. Aqs
Analyze the principle of the Synchronizer (AQS)
The AQS framework incorporates template patterns, such as acquisition and release in exclusive mode, and reentrantlock of unfair locks Nonfairsync examples
/** exclusive access to/FINAL void lock () {if (compareandsetstate (0, 1)) Setexclusiveownerthread (THREAD.C
Urrentthread ());
else acquire (1); Public final void acquire (int arg) {if!tryacquire (ARG) && acquirequeued (Addwaiter (node.ex
clusive), Arg) selfinterrupt (); The Tryacquire () method is not implemented in//aqs, and it is necessary to implement protected Boolean tryacquire (int arg) {throw new Unsupportedoperationex in the subclass
Ception (); Implement protected Final Boolean tryacquire (int acquires) {return nonfairtryacquire in//nonfairsync (acquire
s);
/** Exclusive Release */public void unlock () {sync.release (1);
Public final Boolean release (int arg) {if (Tryrelease (ARG)) {Node h = head;
if (h!= null && h.waitstatus!= 0) unparksuccessor (h);
return true;
return false; Protected Boolean Tryre not implemented in//aqsLease (int arg) {throw new unsupportedoperationexception ();
The protected final Boolean tryrelease (int releases) {int c = getState ()-releases is implemented in the//sync class.
if (Thread.CurrentThread ()!= getexclusiveownerthread ()) throw new Illegalmonitorstateexception ();
Boolean free = false;
if (c = = 0) {free = true;
Setexclusiveownerthread (NULL);
} setstate (c);
return free; }
From the source point of view, the synchronization state of maintenance, acquisition, release action is implemented by subclasses, and subsequent actions into the thread of blocking, wake-up mechanism, etc. by the AQS framework implementation. The
Aqs is implemented using the local method of Locksupport.park () and Locksupport.unpark () to implement thread blocking and wakeup.
private void Unparksuccessor (node node) {/* * If status is negative (i.e., poss ibly needing signal) try * to clear in anticipation of signalling.
It is OK if this * fails or if the status is changed by waiting thread.
*/int ws = Node.waitstatus;
if (WS < 0) compareandsetwaitstatus (node, WS, 0); * * Thread to Unpark are held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled s
Uccessor.
*/Node s = node.next;
if (s = = NULL | | s.waitstatus > 0) {s = null;
for (Node t = tail t!= null && t!= Node t = t.prev) if (t.waitstatus <= 0)
s = t; } if (s!= null) Locksupport.unpark (s.thread); Wake thread}
Aqs internally maintains a queue that encapsulates the blocked thread as an object of the internal class node and maintains it in the queue. Aqs
Private transient volatile Node head; Head node
private transient volatile Node tail; Tail node
This queue is a non-blocking FIFO queue, which is non-blocking when inserting the removal node, so the Aqs internally uses CAs to ensure node insertion and removal of the atom.
/**
* CAS head field. Used only by Enq.
*
Private Final Boolean compareandsethead (Node update) {return
unsafe.compareandswapobject, Headoffset , null, update);
/**
* CAS tail field. Used only by Enq.
*
Private Final Boolean compareandsettail (node expect, node update) {return
unsafe.compareandswapobject ( This, Tailoffset, expect, update);
Node class source code is as follows
The static final class Node {/** tag is a shared mode */static final Node shared = new node ();
The/** tag is exclusive mode/static final Node EXCLUSIVE = null;
/** represents that the thread has been canceled/static final int cancelled = 1;
/** on behalf of subsequent nodes need to wake/static final int SIGNAL =-1;
/** represents a thread waiting for a condition/static final int CONDITION =-2;
static final int PROPAGATE =-3;
volatile int waitstatus;
Volatile Node prev;
Volatile Node next;
volatile thread thread;
The/*** is connected to the next node in the waiting condition. \ node Nextwaiter;
Final Boolean isshared () {return nextwaiter = = SHARED;
Final node predecessor () throws nullpointerexception {node p = prev;
if (p = = null) throw new NullPointerException ();
else return p; Node () {//Used to establish initial head or SHARED marker} NOde (thread thread, Node mode) {//Used by Addwaiter this.nextwaiter = mode;
This.thread = thread;
Node (thread thread, int waitstatus) {//Used by Condition this.waitstatus = Waitstatus;
This.thread = thread; }
}
The
Exclusive synchronization state fetch process, that is, the acquire (int arg) method invocation process, as shown in Figure
is known by the diagram, the predecessor node is the head node and the decision condition to obtain the synchronization state and the thread entering the wait state are the spin processes that obtain the synchronization state of the
. After the synchronization state has been successfully obtained, the current thread returns from the acquire (int arg) method, if
represents the current thread acquiring the lock for the concurrency component.
After the current thread acquires the synchronization state and executes the appropriate logic, the synchronization state needs to be freed so that subsequent nodes can
continue to acquire the synchronization state. The synchronization state can be freed by invoking the release (int arg) method of the Synchronizer, which wakes up its successor node after the
has been put in sync state (and thus the successor node attempts to obtain the synchronization state again).
public final Boolean release (int arg) {//attempt to unlock the lock, Cheng the line that owns the lock to NULL, and set state to 0 if (Tryrelease (AR
g)) {Node h = head;
if (h!= null && h.waitstatus!= 0) unparksuccessor (h);
return true;
return false;
private void Unparksuccessor (node node) {int ws = Node.waitstatus;
if (WS < 0) compareandsetwaitstatus (node, WS, 0);
Node s = node.next;
if (s = = NULL | | s.waitstatus > 0) {s = null;
for (Node t = tail t!= null && t!= Node t = t.prev) if (t.waitstatus <= 0)
s = t;
} if (s!= null) Locksupport.unpark (s.thread); }
After analyzing the process of acquiring and releasing the state of exclusive synchronization, it is necessary to make a summary: When the synchronization state is obtained, the Synchronizer maintains a synchronization queue, and the thread that gets the state failure is added to the queue and spins in the queue, and the condition of the queue (or stop spin) is that the predecessor node is the head node and the synchronization state is successfully obtained. When the synchronization state is released, the Synchronizer calls the tryrelease (int arg) method to release the synchronization state and then wakes the successor node of the head node. Atomic Variable class
An atomic variable class refers to a class under a java.util.concurrent.atomic package.
The following class implementation principles are close to the entire package, and are implemented using volatile and CAs.
Take Atomicinteger for example:
private static final Unsafe Unsafe = Unsafe.getunsafe ();
Private static final long valueoffset;
static {
try {
valueoffset = Unsafe.objectfieldoffset
(AtomicInteger.class.getDeclaredField ("value"));
} catch (Exception ex) {throw new Error (ex);}
}
private volatile int value;
The Atomicinteger class has a unsafe object, which is the key to implementing CAs, and private volatile int value is its current value, with volatile to ensure memory visibility.
Public final Boolean compareandset (int expect, int update) {return
unsafe.compareandswapint (this, Valueoffset, expect, update);
}
This method completes the CAs, and if value==expect, the value is set to update. This is the core approach, and other methods are basically used to implement it
For example
Get old value set new value public
final int getandset (int newvalue) {for
(;;) {
int current = Get ();
if (Compareandset (NewValue)) return to current
;
}
Gets the old value and adds 1 public
final int getandincrement () {for
(;;) {
int current = Get ();
int next = current + 1;
if (Compareandset (next)) return to current
;
}
and other methods.