Java 8 中 CAS 的增強
幾天前,我偶然地將之前寫的用來測試AtomicInteger和synchronized的自增效能的程式碼跑了一下,意外地發現AtomicInteger的效能比synchronized更好了,經過一番原因查詢,有了如下發現:
在jdk1.7中,AtomicInteger的getAndIncrement是這樣的:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }public final boolean compareAndSet(int expect, int update) { return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }
而在jdk1.8中,是這樣的:
public final int getAndIncrement() { return unsafe.getAndAddInt(this, valueOffset, 1); }
可以看出,在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的效能有了大幅提升。
package performance;import java.util.concurrent.CountDownLatch;import java.util.concurrent.atomic.AtomicInteger;import java.util.concurrent.locks.LockSupport;public class AtomicTest { //測試規模,呼叫一次getAndIncreaseX視作提供一次業務服務,記錄提供TEST_SIZE次服務的耗時 private static final int TEST_SIZE = 100000000; //客戶執行緒數 private static final int THREAD_COUNT = 10; //使用CountDownLatch讓各執行緒同時開始 private CountDownLatch cdl = new CountDownLatch(THREAD_COUNT + 1); private int n = 0; private AtomicInteger ai = new AtomicInteger(0); private long startTime; public void init() { startTime = System.nanoTime(); } /** * 使用AtomicInteger.getAndIncrement,測試結果為1.8比1.7有明顯效能提升 * @return */ private final int getAndIncreaseA() { int result = ai.getAndIncrement(); if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } /** * 使用synchronized來完成同步,測試結果為1.7和1.8幾乎無效能差別 * @return */ private final int getAndIncreaseB() { int result; synchronized (this) { result = n++; } if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } /** * 使用AtomicInteger.compareAndSet在java程式碼層面做失敗重試(與1.7的AtomicInteger.getAndIncrement的實現類似), * 測試結果為1.7和1.8幾乎無效能差別 * @return */ private final int getAndIncreaseC() { int result; do { result = ai.get(); } while (!ai.compareAndSet(result, result + 1)); if (result == TEST_SIZE) { System.out.println(System.nanoTime() - startTime); System.exit(0); } return result; } public class MyTask implements Runnable { @Override public void run() { cdl.countDown(); try { cdl.await(); } catch (InterruptedException e) { e.printStackTrace(); } while (true) getAndIncreaseA();// getAndIncreaseB(); } } public static void main(String[] args) throws InterruptedException { AtomicTest at = new AtomicTest(); for (int n = 0; n以下是在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,234jdk1.8
AtomicInteger.getAndIncrement 2,159,486,620
synchronized 4,067,309,911
AtomicInteger.compareAndSet 12,893,188,541補充:應網友要求,在此提供Unsafe.getAndAddInt的相關原始碼以及我的測試程式碼。
用jad反編譯jdk1.8中Unsafe得到的原始碼:
public final int getAndAddInt(Object obj, long l, int i){ int j; do j = getIntVolatile(obj, l); while(!compareAndSwapInt(obj, l, j, j + i)); return j; }public native int getIntVolatile(Object obj, long l);public final native boolean compareAndSwapInt(Object obj, long l, int i, int j);openjdk8的Unsafe原始碼:
public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }public native int getIntVolatile(Object o, long offset);public final native boolean compareAndSwapInt(Object o, long offset, int expected, int x);我的測試程式碼(提示:如果eclipse等ide報錯,那是因為使用了受限的Unsafe,可以將警告級別從error降為warning,具體百度即可):
... import sun.misc.Unsafe;public class AtomicTest { .... private Unsafe unsafe; private long valueOffset; public AtomicTest(){ 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 int getAndIncreaseD(){ int result; 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); } return result; } ... }補充2:對於效能提升的原因,有以下推論,雖不敢說百分之百正確(因為沒有用jvm的原始碼作為論據),但還是有很大把握的,感謝網友@周 可人和@liuxinglanyue!
Unsafe是經過特殊處理的,不能理解成常規的java程式碼,區別在於:
在呼叫getAndAddInt的時候,如果系統底層支援fetch-and-add,那麼它執行的就是native方法,使用的是fetch-and-add;
如果不支援,就按照上面的所看到的getAndAddInt方法體那樣,以java程式碼的方式去執行,使用的是compare-and-swap;這也正好跟openjdk8中Unsafe::getAndAddInt上方的註釋相吻合:
// The following contain CAS-based Java implementations used on// platforms not supporting native instructionsUnsafe的特殊處理也就是我上文所說的“黑魔法”。
相關連結:
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
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/964/viewspace-2809814/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 簡述java中casJava
- 併發的核心:CAS 是什麼?Java8是如何優化 CAS 的?Java優化
- Java 17中對switch的模式匹配增強Java模式
- Java 8: Lambda表示式增強版Comparator和排序Java排序
- MySQL 8 複製效能的增強MySql
- 強大的CAS機制
- Java8-增強版Comparator和排序之Lambda表示式Java排序
- Java中的增強for迴圈(for each)的實現原理與坑Java
- CAS原子操作以及其在Java中的應用Java
- Java排序理解增強版Java排序
- 增強自動化測試的8大技巧
- Java中的鎖原理、鎖優化、CAS、AQS詳解!Java優化AQS
- java高併發系列 - 第21天:java中的CAS操作,java併發的基石Java
- Java 14中對switch的增強,終於可以不寫break了Java
- Java反射增強:程式碼模型Java反射模型
- JAVA基礎--增強for迴圈Java
- Java 16 新特性:instanceof增強Java
- Java高階特性增強-鎖Java
- VS Code 中的增強 code CLI
- Java CAS 原理詳解Java
- Java位元組碼增強技術Java
- Java 8 中的 Random 類Javarandom
- CAS、原子操作類的應用與淺析及Java8對其的優化Java優化
- 增強分析中的智慧問答揭祕
- Java8之Stream-強大的collect操作Java
- 【Java】增強for迴圈,三種迭代的能否刪除Java
- 盤點JAVA中基於CAS實現的原子類, 你知道哪些?Java
- Java8中的Stream APIJavaAPI
- Mybatis 中如何優雅的增強日誌功能?MyBatis
- Java9系列第6篇-Stream流API的增強JavaAPI
- Java多執行緒之CASJava執行緒
- Java併發程式設計-CASJava程式設計
- Java併發(4)- synchronized與CASJavasynchronized
- Java 8 中Stream用法Java
- Java 8 Stream Api 中的 peek 操作JavaAPI
- Java8中的Lambda表示式Java
- Java併發程式設計之Java CAS操作Java程式設計
- 大白話聊聊Java併發面試問題之Java 8如何優化CAS效能?【石杉的架構筆記】Java面試優化架構筆記