Note: ifeve.com the same name article for my hair, this article on its basis to make some adjustments. Reprint please specify the source!
I. Enhancement of CAS in JAVA8
A few days ago, I accidentally wrote previously written to Test Atomicinteger and synchronized's self-increasing performance code ran a bit, unexpectedly found Atomicinteger performance than synchronized better, after some reason to find, with the following findings:
In jdk1.7, the getandincrement of Atomicinteger is this:
Public Final intgetandincrement () { for (;;) { intCurrent =get (); intNext = current + 1; if(Compareandset (current, next))returnCurrent ; }} Public Final BooleanCompareandset (intExpectintupdate) { returnUnsafe.compareandswapint ( This, Valueoffset, expect, update);}
And in jdk1.8, this is the case:
Public Final int getandincrement () { return unsafe.getandaddint (This, valueoffset, 1);}
It can be seen that in jdk1.8, the Getandaddint method of unsafe is used directly, but in jdk1.7 unsafe, there is no such method. It is essential to conclude that the new approach to unsafe is the key to performance improvement. (The end of the article will include some exploratory processes and inferences)
By looking at the source code of Atomicinteger can be found, affected by the Getandadd, Addandget and other methods.
conclusion: With this increase in CAs, we have another reason to use non-blocking algorithms.
Second, the test method
The test code is given below for reference and testing. It should be noted that this test method is simple and rough, compareandset performance is not as good as synchronized, and can not simply say synchronized is better, the use of the two are different, and in practice, there are business processes, There can be no such high competitive strength, this comparison is only a reference, and the test proves that the performance of atomicinteger.getandincrement has improved significantly.
Packageperformance;ImportJava.util.concurrent.CountDownLatch;ImportJava.util.concurrent.atomic.AtomicInteger;ImportJava.util.concurrent.locks.LockSupport;/** * @author[email protected]*/ Public classAtomictest {//test scale, call once getandincreasex to provide a business service to record the time-consuming of providing test_size services Private Static Final intTest_size = 100000000; //Number of client threads Private Static Final intThread_count = 10; //use Countdownlatch to let threads start at the same time PrivateCountdownlatch CDL =NewCountdownlatch (Thread_count + 1); Private intn = 0; PrivateAtomicinteger ai =NewAtomicinteger (0); Private LongStartTime; Public voidinit () {StartTime=System.nanotime (); } /*** with atomicinteger.getandincrement, the test result is 1.8:1.7 significant performance improvement *@return */ Private Final intGetandincreasea () {intresult =ai.getandincrement (); if(Result = =test_size) {System.out.println (System.nanotime ()-startTime); System.exit (0); } returnresult; } /*** Using synchronized to complete synchronization, test results of 1.7 and 1.8 almost no performance difference *@return */ Private Final intGetandincreaseb () {intresult; synchronized( This) {result= n++; } if(Result = =test_size) {System.out.println (System.nanotime ()-startTime); System.exit (0); } returnresult; } /*** Use Atomicinteger.compareandset to do a failure retry at the Java code level (similar to the implementation of the 1.7 atomicinteger.getandincrement), * test results of 1.7 and 1.8 almost no performance difference * @return */ Private Final intgetandincreasec () {intresult; Do{result=Ai.get (); } while(!ai.compareandset (result, result + 1)); if(Result = =test_size) {System.out.println (System.nanotime ()-startTime); System.exit (0); } returnresult; } Public classMyTaskImplementsRunnable {@Override Public voidrun () {Cdl.countdown (); Try{cdl.await (); } Catch(interruptedexception e) {e.printstacktrace (); } while(true) Getandincreasea ();//Getandincreaseb (); } } Public Static voidMain (string[] args)throwsinterruptedexception {atomictest at=Newatomictest (); for(intn = 0; n < Thread_count; n++) NewThread (at.NewMyTask ()). Start (); System.out.println ("Start"); At.init (); At.cdl.countDown (); }}
The following are the test results under the Intel (R) Core (TM) I7-4710HQ CPU @2.50ghz (quad core Eight thread) (less volatile, so each item is tested only four or five times, whichever is the more intermediate value):
jdk1.7atomicinteger.getandincrement 12,653,757,034synchronized 4,146,813,462atomicinteger.compareandset 12,952,821,234jdk1.8atomicinteger.getandincrement 2,159,486,620synchronized 4,067,309,911atomicinteger.compareandset 12,893,188,541
The exploration and inference of the reason of promotion
At first, I suspect that in 1.8, unsafe uses the native method directly, and 1.7 is the failure retry in Getandincrement, that is, in the Java code level, so the performance difference, so I used jad to decompile the unsafe, get the following code:
Public Final intGetandaddint (Object obj,LongLinti) { intJ; DoJ=getintvolatile (obj, L); while(!compareandswapint (obj, L, J, J +i)); returnJ;} Public native intGetintvolatile (Object obj,Longl); Public Final native BooleanCompareandswapint (Object obj,LongLintIintj);
and reference the openjdk8 of the unsafe source code:
Public Final intGetandaddint (Object o,LongOffsetintDelta) { intv; Do{v=getintvolatile (o, offset); } while(!compareandswapint (o, offset, V, v +delta)); returnv;} Public native intGetintvolatile (Object o,Longoffset); Public Final native BooleanCompareandswapint (Object o,LongOffset,intexpected,intx);
As can be seen from the above information, 1.8, the failure retry is also at the Java code level (the difference is transferred to the unsafe Java method inside), is to overturn my guess, so I decided to use reflection, direct access to the unsafe instance, Write the same code as the Unsafe.getandaddint method to test to see if you can find some new clues:
...ImportSun.misc.Unsafe; Public classatomictest {....Privateunsafe unsafe; Private LongValueoffset; Publicatomictest () {Field F; Try{f= Unsafe.class. Getdeclaredfield ("Theunsafe"); F.setaccessible (true); Unsafe= (Unsafe) f.get (NULL); Valueoffset= Unsafe.objectfieldoffset (Atomicinteger.class. Getdeclaredfield ("value")); }Catch(nosuchfieldexception e) {...} } Private Final intgetandincreased () {intresult; Do{result=unsafe.getintvolatile (AI, valueoffset); } while(!unsafe.compareandswapint (AI, valueoffset, result, result+1)); if(Result = =MAX) {System.out.println (System.nanotime ()-startTime); System.exit (0); } returnresult; } ...}
But what is disappointing is that this is the same as the getandincrement efficiency of 1.7, clearly the same as the 1.8 Unsafe.getandaddint method, but it is very different efficiency.
Finally, after the ifeve.com of the users of the guidance, the performance of the reasons for the promotion of the following inference, although not to say absolutely correct (because there is no use of the JVM source as an argument), but still have a lot of confidence, thank netizens @ Zhou and @liuxinglanyue!
Unsafe is specially handled and cannot be understood as a regular Java code, except that:
1.8 When calling Getandaddint, if the system support Fetch-and-add, then it executes native method, using Fetch-and-add;
RELATED links:
Http://ashkrit.blogspot.com/2014/02/atomicinteger-java-7-vs-java-8.html
Http://hg.openjdk.java.net/jdk8u/hs-dev/jdk/file/a006fa0a9e8f/src/share/classes/sun/misc/Unsafe.java
The enhancement of CAs in JAVA8