Prior to JDK 5, the Java language was guaranteed to be synchronized by the Synchronized keyword, which would result in a lock
The lock mechanism has the following issues:
(1) Under the multi-thread competition, the locking and release lock will lead to more context switching and scheduling delay, causing performance problems.
(2) A thread holding a lock causes all other threads that require this lock to hang.
(3) If a high-priority thread waits for a thread with a lower priority to release the lock, it causes the priority to be inverted, causing a performance risk.
Volatile is a good mechanism, but volatile does not guarantee atomicity. Therefore, for synchronization to eventually return to the lock mechanism.
An exclusive lock is a pessimistic lock, and synchronized is an exclusive lock that causes all other threads that need to be locked to hang, waiting for the thread that holds the lock to release the lock. Another more effective lock is the optimistic lock. The so-called optimistic lock is that each time without locking but assuming that there is no conflict to complete an operation, if the conflict failed to retry until successful. The mechanism used for optimistic locking is Cas,compare and Swap.
First, what is CAs
Cas,compare and swap abbreviations, Chinese translation into comparison and exchange.
We all know that before the Java language, concurrency has been widespread and has been used extensively in the server world. So hardware vendors have long been in the chip to add a large number of primitives until the concurrent operation of the original language, in order to improve the hardware level efficiency. In Intel's CPU, use the CMPXCHG directive.
In the early days of Java development, the Java language was not able to take advantage of the hardware provided by these facilities to improve the performance of the system. And as Java continues to evolve, the advent of the Java Local method (JNI) has made it easy for Java programs to invoke local methods directly over the JVM, so that Java is also more in the way of concurrency. In the CUCURENCT package provided by Doug Lea, CAS theory is the cornerstone of its implementation of the entire Java package.
The CAS operation consists of three operands-the memory location (V), the expected original value (A), and the new value (B). If the value of the memory location matches the expected original value, the processor automatically updates the location value to the new value. Otherwise, the processor does nothing. In either case, it will return the value of that location before the CAS directive. (In some special cases of CAs, only the CAS will be returned successfully, not the current value.) CAS effectively illustrates that "I think position V should contain a value of A; If you include this value, B will be placed in this position; otherwise, do not change the position, just tell me the value of this position now." ”
CAS is typically used for synchronization by reading value A from address V, performing a multi-step calculation to obtain the new value B, and then using CAs to change the value of V from A to B. If the value at V has not changed at the same time, the CAS operation succeeds.
CAS-like directives allow the algorithm to perform a read-modify-write operation without fear of other threads modifying the variable at the same time, because if another thread modifies the variable, the CAS detects it (and fails) and the algorithm can recalculate the operation.
Ii. purpose of CAs
The CPU's CAS instruction is used, and the non-blocking algorithm of Java is accomplished by using JNI. Other atomic operations are done using a similar feature. And the whole j.u.c is built on the CAs, so for synchronized blocking algorithm, J.U.C has a great improvement in performance.
Iii. problems in CAs
CAS is an efficient solution to atomic operations, but CAS still has three major problems. ABA problem, long cycle time overhead and guaranteed atomic operation of only one shared variable
1. ABA issues. Because CAs needs to check that the value is not changed when the value is manipulated, if it does not change, it is updated, but if a value is a, B, and a, then checking with CAS will show that its value has not changed, but actually it has changed. The solution to the ABA problem is to use the version number. Append the version number before the variable, and add one to the version number each time the variable is updated, then A-b-a will become 1a-2b-3a.
A class atomicstampedreference is provided in the atomic package of the JDK starting with Java1.5 to solve the ABA problem. The Compareandset method of this class is to first check whether the current reference is equal to the expected reference, and whether the current flag is equal to the expected flag, and if all is equal, then atomically sets the reference and the value of the flag to the given update value.
About ABA Issues Reference document: Http://blog.hesey.net/2011/09/resolve-aba-by-atomicstampedreference.html
2. Long cycle time overhead. Spin CAs can cause very large execution overhead for CPUs if they are unsuccessful for a long time. If the JVM can support the pause instruction provided by the processor then the efficiency will be improved, the pause command has two functions, first it can delay the pipelining instruction (de-pipeline), so that the CPU does not consume excessive execution resources, the delay depends on the specific implementation of the version, On some processors, the delay time is zero. Second, it avoids the CPU pipelining being emptied (CPU pipeline flush) when exiting the loop due to memory order collisions (violation), which improves CPU execution efficiency.
3. Only one atomic operation of a shared variable can be guaranteed. When performing operations on a shared variable, we can use the method of circular CAs to guarantee atomic operations, but for multiple shared variables, the cyclic CAS cannot guarantee the atomicity of the operation, it is possible to use locks at this time, or there is a trickery way to combine multiple shared variables into a single shared variable to operate. For example, there are two shared variable i=2,j=a, merge ij=2a, and then use CAs to manipulate IJ. Starting with Java1.5 The JDK provides the Atomicreference class to guarantee atomicity between reference objects, and you can put multiple variables in an object for CAS operations.
Four, the realization of concurrent package
Because Java's CAs have both volatile read and volatile write memory semantics, communication between Java threads now has the following four ways:
- A thread writes the volatile variable, and then the B thread reads the volatile variable.
- A thread writes the volatile variable, and then the B thread updates the volatile variable with CAs.
- A thread updates a volatile variable with CAS, and then the B thread updates the volatile variable with CAs.
- A thread updates a volatile variable with CAS, and then the B thread reads the volatile variable.
The CAs in Java use the high-efficiency machine-level atomic instructions available on modern processors that atomically perform read-and-write operations on memory, which is the key to achieving synchronization in a multiprocessor (essentially, a computer that supports atomic read-change-write instructions, is an asynchronous equivalent machine for calculating Turing machines sequentially, so any modern multiprocessor will support some atomic instruction that performs atomic read-and-write operations on memory. At the same time, the read/write and CAS of volatile variables can implement communication between threads. The integration of these features forms the cornerstone of the entire concurrent package. If we carefully analyze the source code implementation of the concurrent package, we will find a generalized implementation pattern:
- First, declare the shared variable to be volatile;
- Then, the synchronization between threads is realized by using the atomic condition update of CAs.
- At the same time, the communication between threads is implemented with volatile read/write and the volatile reading and writing memory semantics of CAs.
AQS, Non-blocking data structures and atomic variable classes (classes in the Java.util.concurrent.atomic package), the underlying classes in these concurrent packages, are implemented using this pattern, and the high-level classes in the concurrent package are dependent on these base classes for implementation. Overall, the concurrent package is implemented as follows:
Multi-thread CAs