Java8中CAS的增強
幾天前,我偶然地將之前寫的用來測試AtomicInteger和synchronized的自增效能的程式碼跑了一下,意外地發現AtomicInteger的效能比synchronized更好了,經過一番原因查詢,有了如下發現:
在jdk1.7中,AtomicInteger的getAndIncrement是這樣的:
01 |
public final int getAndIncrement() {
|
02 |
for (;;) {
|
03 |
int current = get();
|
04 |
int next = current + 1 ;
|
05 |
if (compareAndSet(current, next))
|
06 |
return current;
|
07 |
}
|
08 |
} |
09 |
public final boolean compareAndSet( int expect, int update) {
|
10 |
return unsafe.compareAndSwapInt( this , valueOffset, expect, update);
|
11 |
} |
而在jdk1.8中,是這樣的:
1 |
public final int getAndIncrement() {
|
2 |
return unsafe.getAndAddInt( this , valueOffset, 1 );
|
3 |
} |
可以看出,在jdk1.8中,直接使用了Unsafe的getAndAddInt方法,而在jdk1.7的Unsafe中,沒有此方法。(PS:為了找出原因,我反編譯了Unsafe,發現CAS的失敗重試就是在getAndAddInt方法裡完成的,我用反射獲取到Unsafe例項,編寫了跟getAndAddInt相同的程式碼,但測試結果卻跟jdk1.7的getAndIncrement一樣慢,不知道Unsafe裡面究竟玩了什麼黑魔法,還請高人不吝指點)(補充:文章末尾已有推論)
通過檢視AtomicInteger的原始碼可以發現,受影響的還有getAndAdd、addAndGet等大部分方法。
有了這次對CAS的增強,我們又多了一個使用非阻塞演算法的理由。
最後給出測試程式碼,需要注意的是,此測試方法簡單粗暴,compareAndSet的效能不如synchronized,並不能簡單地說synchronized就更好,兩者的使用方式是存在差異的,而且在實際使用中,還有業務處理,不可能有如此高的競爭強度,此對比僅作為一個參考,該測試能夠證明的是,AtomicInteger.getAndIncrement的效能有了大幅提升。
01 |
package performance;
|
02 |
03 |
import java.util.concurrent.CountDownLatch;
|
04 |
import java.util.concurrent.atomic.AtomicInteger;
|
05 |
import java.util.concurrent.locks.LockSupport;
|
06 |
07 |
public class AtomicTest {
|
08 |
//測試規模,呼叫一次getAndIncreaseX視作提供一次業務服務,記錄提供TEST_SIZE次服務的耗時
|
09 |
private static final int TEST_SIZE = 100000000 ;
|
10 |
//客戶執行緒數
|
11 |
private static final int THREAD_COUNT = 10 ;
|
12 |
//使用CountDownLatch讓各執行緒同時開始
|
13 |
private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1 );
|
14 |
15 |
private int n = 0 ;
|
16 |
private AtomicInteger ai = new AtomicInteger( 0 );
|
17 |
private long startTime;
|
18 |
19 |
public void init() {
|
20 |
startTime = System.nanoTime();
|
21 |
}
|
22 |
23 |
/**
|
24 |
* 使用AtomicInteger.getAndIncrement,測試結果為1.8比1.7有明顯效能提升
|
25 |
* @return
|
26 |
*/
|
27 |
private final int getAndIncreaseA() {
|
28 |
int result = ai.getAndIncrement();
|
29 |
if (result == TEST_SIZE) {
|
30 |
System.out.println(System.nanoTime() - startTime);
|
31 |
System.exit( 0 );
|
32 |
}
|
33 |
return result;
|
34 |
}
|
35 |
36 |
/**
|
37 |
* 使用synchronized來完成同步,測試結果為1.7和1.8幾乎無效能差別
|
38 |
* @return
|
39 |
*/
|
40 |
private final int getAndIncreaseB() {
|
41 |
int result;
|
42 |
synchronized ( this ) {
|
43 |
result = n++;
|
44 |
}
|
45 |
if (result == TEST_SIZE) {
|
46 |
System.out.println(System.nanoTime() - startTime);
|
47 |
System.exit( 0 );
|
48 |
}
|
49 |
return result;
|
50 |
}
|
51 |
52 |
/**
|
53 |
* 使用AtomicInteger.compareAndSet在java程式碼層面做失敗重試(與1.7的AtomicInteger.getAndIncrement的實現類似),
|
54 |
* 測試結果為1.7和1.8幾乎無效能差別
|
55 |
* @return
|
56 |
*/
|
57 |
private final int getAndIncreaseC() {
|
58 |
int result;
|
59 |
do {
|
60 |
result = ai.get();
|
61 |
} while (!ai.compareAndSet(result, result + 1 ));
|
62 |
if (result == TEST_SIZE) {
|
63 |
System.out.println(System.nanoTime() - startTime);
|
64 |
System.exit( 0 );
|
65 |
}
|
66 |
return result;
|
67 |
}
|
68 |
69 |
public class MyTask implements Runnable {
|
70 |
@Override
|
71 |
public void run() {
|
72 |
cdl.countDown();
|
73 |
try {
|
74 |
cdl.await();
|
75 |
} catch (InterruptedException e) {
|
76 |
e.printStackTrace();
|
77 |
}
|
78 |
while ( true )
|
79 |
getAndIncreaseA(); // getAndIncreaseB();
|
80 |
}
|
81 |
}
|
82 |
83 |
public static void main(String[] args) throws InterruptedException {
|
84 |
AtomicTest at = new AtomicTest();
|
85 |
for ( int n = 0 ; n < THREAD_COUNT; n++)
|
86 |
new Thread(at. new MyTask()).start();
|
87 |
System.out.println( "start" );
|
88 |
at.init();
|
89 |
at.cdl.countDown();
|
90 |
}
|
91 |
} |
以下是在Intel(R) Core(TM) i7-4710HQ CPU @2.50GHz(四核八執行緒)下的測試結果(波動較小,所以每項只測試了四五次,取其中一個較中間的值):
jdk1.7
AtomicInteger.getAndIncrement 12,653,757,034
synchronized 4,146,813,462
AtomicInteger.compareAndSet 12,952,821,234
jdk1.8
AtomicInteger.getAndIncrement 2,159,486,620
synchronized 4,067,309,911
AtomicInteger.compareAndSet 12,893,188,541
補充:應網友要求,在此提供Unsafe.getAndAddInt的相關原始碼以及我的測試程式碼。
用jad反編譯jdk1.8中Unsafe得到的原始碼:
01 |
public final int getAndAddInt(Object obj, long l, int i)
|
02 |
{ |
03 |
int j;
|
04 |
do
|
05 |
j = getIntVolatile(obj, l);
|
06 |
while (!compareAndSwapInt(obj, l, j, j + i));
|
07 |
return j;
|
08 |
} |
09 |
public native int getIntVolatile(Object obj, long l);
|
10 |
public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);
|
openjdk8的Unsafe原始碼:
01 |
public final int getAndAddInt(Object o, long offset, int delta) {
|
02 |
int v;
|
03 |
do {
|
04 |
v = getIntVolatile(o, offset);
|
05 |
} while (!compareAndSwapInt(o, offset, v, v + delta));
|
06 |
return v;
|
07 |
} |
08 |
public native int getIntVolatile(Object o, long offset);
|
09 |
public final native boolean compareAndSwapInt(Object o, long offset,
|
10 |
int expected,
|
11 |
int x);
|
我的測試程式碼(提示:如果eclipse等ide報錯,那是因為使用了受限的Unsafe,可以將警告級別從error降為warning,具體百度即可):
01 |
... |
02 |
import sun.misc.Unsafe;
|
03 |
public class AtomicTest {
|
04 |
....
|
05 |
private Unsafe unsafe;
|
06 |
private long valueOffset;
|
07 |
public AtomicTest(){
|
08 |
Field f;
|
09 |
try {
|
10 |
f = Unsafe. class .getDeclaredField( "theUnsafe" );
|
11 |
f.setAccessible( true );
|
12 |
unsafe = (Unsafe)f.get( null );
|
13 |
valueOffset = unsafe.objectFieldOffset(AtomicInteger. class .getDeclaredField( "value" ));
|
14 |
} catch (NoSuchFieldException e){
|
15 |
...
|
16 |
}
|
17 |
}
|
18 |
private final int getAndIncreaseD(){
|
19 |
int result;
|
20 |
do {
|
21 |
result = unsafe.getIntVolatile(ai, valueOffset);
|
22 |
} while (!unsafe.compareAndSwapInt(ai, valueOffset, result, result+ 1 ));
|
23 |
if (result == MAX){
|
24 |
System.out.println(System.nanoTime()-startTime);
|
25 |
System.exit( 0 );
|
26 |
}
|
27 |
return result;
|
28 |
}
|
29 |
...
|
30 |
} |
補充2:對於效能提升的原因,有以下推論,雖不敢說百分之百正確(因為沒有用jvm的原始碼作為論據),但還是有很大把握的,感謝網友@周 可人和@liuxinglanyue!
Unsafe是經過特殊處理的,不能理解成常規的java程式碼,區別在於:
在呼叫getAndAddInt的時候,如果系統底層支援fetch-and-add,那麼它執行的就是native方法,使用的是fetch-and-add;
如果不支援,就按照上面的所看到的getAndAddInt方法體那樣,以java程式碼的方式去執行,使用的是compare-and-swap;
這也正好跟openjdk8中Unsafe::getAndAddInt上方的註釋相吻合:
1 |
// The following contain CAS-based Java implementations used on |
2 |
// platforms not supporting native instructions |
Unsafe的特殊處理也就是我上文所說的“黑魔法”。
相關連結:
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
相關文章
- Java 8 中 CAS 的增強Java
- 併發的核心:CAS 是什麼?Java8是如何優化 CAS 的?Java優化
- 強大的CAS機制
- VS Code 中的增強 code CLI
- CAS、原子操作類的應用與淺析及Java8對其的優化Java優化
- Java 17中對switch的模式匹配增強Java模式
- 增強分析中的智慧問答揭祕
- Java8之Stream-強大的collect操作Java
- Mybatis 中如何優雅的增強日誌功能?MyBatis
- Java8中的Stream APIJavaAPI
- 簡述java中casJava
- Ubuntu在Vbox中安裝增強功能Ubuntu
- Java8中的Lambda表示式Java
- SAP 產品一脈相承的 UI 增強思路,在 SAP電商雲 UI 增強實現中的體現UI
- Java中的增強for迴圈(for each)的實現原理與坑Java
- Spring Boot中增強對MongoDB的配置(連線池等)Spring BootMongoDB
- 淺談java8中的流的使用Java
- Java8中的時間處理Java
- Java8中的 lambda 和Stream APIJavaAPI
- 10G FGA的增強
- 增強for 迴圈
- Java8中Stream 的一些用法Java
- CAS原子操作以及其在Java中的應用Java
- Oracle Database 19c 中的 JSON_OBJECT 函式的增強功能OracleDatabaseJSONObject函式
- CAS
- MySQL 8 複製效能的增強MySql
- 增強型evssl證書的作用
- 如何增強grpc的攔截器RPC
- 使用PWA增強你的github pagesGithub
- Dubbo剖析-增強SPI的實現
- Java 14中對switch的增強,終於可以不寫break了Java
- 影像增強(Image enhancement)
- openGauss DSS功能增強
- TotalFinder for MacFinder增強工具Mac
- IDEA 2024.1:Spring支援增強、GitHub Action支援增強、更新HTTP Client等IdeaSpringGithubHTTPclient
- Java中的鎖原理、鎖優化、CAS、AQS詳解!Java優化AQS
- 更便捷的Mybatis增強外掛——EasyMybatisMyBatis
- ABAP 740新的OPEN SQL增強特性SQL
- C# 9 新特性 —— 增強的 foreachC#