Cas,compare and swap, that is, compare and swap. Doug Lea, the Great God, uses CAS technology extensively in synchronous components to implement Java Multi-threading concurrency. The entire AQS synchronization component, atomic Atomic class operations, and so on are all implemented in CAs, even concurrenthashmap in the 1.8 version to cas+synchronized. It can be said that CAS is the cornerstone of the whole JUC.
CAS analysis
There are three parameters in CAs: Memory value V, old expected value A, value B to be updated, and only if the value of the memory value v equals the old expected value of a, the value of the memory value V is modified to B, otherwise nothing is done. Its pseudo-code is as follows:
if(this.value == A){ this.value = B returntrue;}else{ returnfalse;}
The atomic class under
Juc is implemented by CAS, and the following is an example of atomicinteger to illustrate the implementation of CAs. as follows:
private static final unsafe unsafe = unsafe.getunsafe (); private static final long valueoffset; static {try {valueoffset = Unsafe.objectfieldoffset (atomici Nteger.class.getDeclaredField ( "value" )); } catch (Exception ex) {throw new Error (ex); }} private volatile int value;
Unsafe is the core class of CAs, and Java cannot directly access the underlying operating system, but is accessed through the local (native) method. However, the JVM has opened a backdoor: Unsafe, which provides hardware-level atomic operations.
Valueoffset is the offset address of the variable value in memory, and unsafe is to get the original value of the data by the offset address.
Value, using the volatile modifier, to ensure that the same is seen in a multithreaded environment.
We will use Atomicinteger's Addandget () method to illustrate, first look at the source code:
Public Final int Addandget(intDelta) {returnUnsafe.getandaddint ( This, Valueoffset, Delta) + Delta; } Public Final int Getandaddint(Object var1,LongVAR2,intVAR4) {intVAR5; do {VAR5 = This. Getintvolatile (var1, var2); } while(! This. Compareandswapint (Var1, Var2, VAR5, VAR5 + VAR4));returnVAR5; }
The Getandaddint method of calling unsafe internally, in the Getandaddint method is mainly to see the Compareandswapint method:
publicfinalnativebooleancompareAndSwapIntlongintint var5);
The method is a local method, there are four parameters, representing: Object, object address, expected value, modified value (a partner told me that he asked me when he was interviewing what the four variables mean ... +_+). The implementation of the method here does not do a detailed introduction, interested partners can see the source of OpenJDK.
CAs can guarantee that a read-write operation is an atomic operation, which is easy to implement on a single processor, but it is a bit more complex to implement on a multiprocessor.
The CPU provides two ways to implement multi-processor atomic operations: Bus lock or cache lock.
bus lock : Bus lock is the use of a processor to provide a lock# signal, when a processor output this signal on the bus, the other processor requests will be blocked, then the processor can exclusive use of shared memory. But this approach seems a bit overbearing, not good, he put the CPU and memory communication between the lock, during the lock, other processors can not other memory address data, its overhead is a bit large. So there's a cache lock.
Cache lock : In fact, the needle for the above situation we only need to ensure that at the same time the operation of a memory address is atomic. Cache locking is the data that is cached in the memory area if, during lock-up, when it performs a lock operation to write back to memory, the processor does not output the lock# signal, but modifies the internal memory address, using the cache consistency protocol to guarantee atomicity. The cache consistency mechanism ensures that the data in the same memory area can be modified only by one processor, meaning that when CPU1 modifies I in the cache row, the cache lock is used, then CPU2 cannot cache the I cache rows at the same time.
CAS defects
Although CAs efficiently solves atomic operations, there are still some defects, mainly in three methods: the cycle time is too long, only a shared variable atomic operation, ABA problem can be guaranteed.
Cycle time too long
What if the CAs have not been successful? This is absolutely possible, and if the spin CAs are unsuccessful for a long time, it can be very costly to the CPU. There are places in Juc that limit the number of CAs spins, such as Blockingqueue's synchronousqueue.
Only one shared variable atomic operation can be guaranteed
See the implementation of the CAS know that this can only be for a shared variable, if it is a number of shared variables can only use the lock, of course, if you have a way to make multiple variables into a variable, the use of CAS is also good. such as the high status of State in read-write locks
ABA Issues
CAS needs to check that the action value has not changed and is updated if no change has occurred. But there is a situation where if a value turns out to be a, b and then a, then the CAs check will find no change, but in essence it has changed, which is called the ABA problem. The solution to the ABA problem is to add a version number, which adds a version number to each variable, plus 1 for each change, that is, a-> b-> A, which becomes 1a-> 2b-> 3 A.
Use an example to illustrate the effects of the ABA problem.
Have the following linked list
If we want to replace B with a, that is Compareandset (this,a,b). Thread 1 performs b replacement A operation, thread 2 mainly performs the following actions, A, b out of the stack, and then C, A into the stack, and finally the list is as follows:
After the completion of Thread 1 discovery is still a, then Compareandset (this,a,b) success, but there will be a problem is B.next = Null,compareandset (this,a,b), will cause C loss, stack only a B element, C was lost for no-for-all.
CAs, the problem of ABA hidden, the solution is the version number, Java provides atomicstampedreference to solve. Atomicstampedreference avoids ABA problems by wrapping the tuple of [E,integer] to stamp the OBJECT tag version stamp. Thread 1 should fail for the above case.
Atomicstampedreference's Compareandset () method is defined as follows:
public boolean compareandset (V expectedreference, V newreference, int Expectedstamp, int newstamp) {pair<v> current = Pair; return expectedreference = current.reference && expecteds Tamp = = Current.stamp && (newreference = = current.reference && Newstamp = Curren T.stamp) | | Caspair (Current, Pair.of (Newreference, Newstamp))); }
Compareandset has four parameters, respectively: expected reference, updated reference, expected flag, updated flag. The source department is good at understanding the expected reference = = Current reference, the expected identity = = Current identity, if the updated reference and flag and the current reference and flag are equal, returns true directly, otherwise a new pair object is generated by the pair and replaced with the current pair CAs. Pair is the inner class of atomicstampedreference, which is used primarily for record references and version stamp information (identities), defined as follows:
Private Static class Pair<T> { FinalT reference;Final intStampPrivate Pair(T Reference,intStamp) { This. reference = reference; This. Stamp = stamp; }Static<T> pair<t> of (T reference,intStamp) {return NewPair<t> (reference, stamp); } }Private volatilePair<v> pair;
The pair records the object's reference and version stamp, and the version stamp is of type int, preserving its own increment. At the same time, the pair is an immutable object, all of its properties are defined as final, and a method is provided that returns a new Pari object. The pair object is defined as volatile and ensures visibility in a multithreaded environment. In Atomicstampedreference, most of the methods are to generate a new pair object by invoking the of method of the pair, and then assign the value to the variable pair. such as the Set method:
publicvoidsetint newStamp) { Pair<V> current = pair; if (newReference != current.reference || newStamp != current.stamp) this.pair = Pair.of(newReference, newStamp); }
Below we will be able to see the difference between atomicstampedreference and atomicinteger through an example. We define two threads, thread 1 is responsible for 100-> 110-> 100, Thread 2 executes 100->120, to see the difference between the two.
Public class Test { Private StaticAtomicinteger Atomicinteger =NewAtomicinteger ( -);Private StaticAtomicstampedreference atomicstampedreference =NewAtomicstampedreference ( -,1); Public Static void Main(string[] args)throwsinterruptedexception {//atomicintegerThread AT1 =NewThread (NewRunnable () {@Override Public void Run() {Atomicinteger.compareandset ( -, the); Atomicinteger.compareandset ( the, -); } }); Thread AT2 =NewThread (NewRunnable () {@Override Public void Run() {Try{TimeUnit.SECONDS.sleep (2);//AT1, complete}Catch(Interruptedexception e) {E.printstacktrace (); } System.out.println ("Atomicinteger:"+ Atomicinteger.compareandset ( -, -)); } }); At1.start (); At2.start (); At1.join (); At2.join ();//atomicstampedreferenceThread TSF1 =NewThread (NewRunnable () {@Override Public void Run() {Try{//Let TSF2 get stamp first, resulting in inconsistent expected timestampTimeUnit.SECONDS.sleep (2); }Catch(Interruptedexception e) {E.printstacktrace (); }//Expected reference: 100, updated reference: 110, expected identity Getstamp () Updated identity getstamp () + 1Atomicstampedreference.compareandset ( -, the, Atomicstampedreference.getstamp (), Atomicstampedreference.getstamp () +1); Atomicstampedreference.compareandset ( the, -, Atomicstampedreference.getstamp (), Atomicstampedreference.getstamp () +1); } }); Thread TSF2 =NewThread (NewRunnable () {@Override Public void Run() {intStamp = Atomicstampedreference.getstamp ();Try{TimeUnit.SECONDS.sleep (2);//thread TSF1 finished executing}Catch(Interruptedexception e) {E.printstacktrace (); } System.out.println ("Atomicstampedreference:"+atomicstampedreference.compareandset ( -, -, Stamp,stamp +1)); } }); Tsf1.start (); Tsf2.start (); }}
Operation Result:
The result of operation shows the ABA problem of Atomicinteger and the atomicstampedreference to solve ABA problem adequately.
"Dead java Concurrency"----in-depth analysis of CAs