Java多執行緒之—Synchronized方式和CAS方式實現執行緒安全效能對比
效能比較猜想
1.大膽假設
在設計試驗方法之前,針對Synchronized和CAS兩種方式的特點,我們先來思考一下兩種方式效率如何?
首先,我們在回顧一下兩種方式是如何保證執行緒安全的。Synchronized方式通過大家應該很熟悉,他的行為非常悲觀,只要有一個執行緒進入Synchronized臨界區域(確保不被多執行緒併發訪問的區域),其他執行緒均不能進入,直到早先進入的執行緒退出臨界區域。和Synchronized相比CAS演算法則顯得樂觀多了,他不限制其他執行緒進入臨界區域,但是當一個執行緒退出臨界區域的時候,他必須檢查臨界區域內資料是否被其他執行緒修改,一旦被修改,此執行緒就要做重試操作。
我們舉一個生活化的例子加深理解:
我們把執行緒比作在馬路上行駛的汽車,臨界區比作道路交叉的十字路口。
如果所有馬路上只有一輛車(單執行緒情況),那麼我們無需任何處理。如果馬路上不只一輛車要通過十字路口(多執行緒情況),並且我們不允許車輛在十字路口相撞(執行緒衝突情況),那麼我們必須需要做出一些限制來避免同時通過十字路口的車輛相互碰撞(保證執行緒安全)。Synchronized方式相當於在路口設定紅綠燈,用“紅燈停,綠燈行”的基本原則限制兩側路口的汽車同時進入十字路口。而CAS方式就要評司機自覺了,一旦一輛汽車進入十字路口後發現已經有另一輛汽車進入十字路口,他需要退出十字路口重新進入。
我們用生活經驗想象一下兩種方式的車輛通行效率,我們經常看到在車流不高的路口汽車白白等待紅綠燈,顯然在車輛比較少的路口設定紅綠燈很有可能影響通行效率,所有晚上一旦車流下降,某些路口紅綠燈會關閉以調高通過效率。我們也看到在某個高峰時段由於路口紅綠燈損壞造成的車輛擁堵,這說明在車流量較多的情況下,紅綠燈的使用恰恰能避免擁堵發生。
通過紅綠燈的例子我們可以假設,當執行緒競爭比較少的情況下,CAS演算法效率較高,反之,Synchronized方式效率較高。
2.小心求證
借用上文中兩種“棧”的程式碼,構建測試方法:
public static void main(String[] args) {
long amount = 0;
int max = 1000;
for (int k = 0; k < max; k++) {
long start =System.nanoTime();
int loops = 1000;
//分別執行不同的程式數1、2、、4、8、16、32、64...
int threads =1;
//分別執行不同的Stack類。
//SynchronizedStack<String> stack = new SynchronizedStack();
TreiberStack<String> stack=new TreiberStack();
ExecutorService pool = Executors.newCachedThreadPool();
for (int j = 0; j < threads; j++) {
pool.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < loops; i++) {
stack.push("a");
}
}
});
}
pool.shutdown();
try {
pool.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
}
long end = System.nanoTime();
System.out.println("每次用時:" + (end - start));
amount += end - start;
}
System.out.println("平均用時:" + amount / max);
}
設定不同的threads
的值並切換SynchronizedStack
類或者TreiberStack
類後執行結果如下:
threads/stack | SynchronizedStack | TreiberStack |
---|---|---|
1 | 259130 | 263106 |
2 | 414647 | 409145 |
4 | 596424 | 534784 |
8 | 1087788 | 1098736 |
16 | 1502044 | 1713802 |
32 | 2524017 | 3345929 |
64 | 4573564 | 7033072 |
128 | 8469581 | 14803696 |
256 | 17661089 | 30156804 |
512 | 35128364 | 63126440 |
線上程數較少,競爭較少的情況下TreiberStack
和SynchronizedStack
執行結果差距很小,但是隨著執行緒數的增多,競爭加劇,TreiberStack
較SynchronizedStack
執行時間明顯延長。
為什麼線上程數較少的情況下TreiberStack
和SynchronizedStack
沒有明顯差別?
在JDK1.6以後對synchronized
關鍵字做了優化,導致加鎖的效率提升,所以和非阻塞方式相比效率也不會相差很多。
為什麼線上程數較多的情況下TreiberStack
和SynchronizedStack
差別越來越大?
主要原因在於TreiberStack
在高併發的情況下會產生大量的競爭,造成大量重試操作。
我們改造一下TreiberStack
類,演示這種情況:
public class TreiberStack<E> {
private AtomicReference<Node<E>> headNode = new AtomicReference<>();
//記錄實際執行次數
public static final LongAdder adder=new LongAdder();
public void push(E item) {
Node<E> newHead = new Node<>(item);
Node<E> oldHead;
do {
adder.increment();
oldHead = headNode.get();
newHead.next = oldHead;
} while (!headNode.compareAndSet(oldHead, newHead));
}
public E pop() {
Node<E> oldHead;
Node<E> newHead;
do {
oldHead = headNode.get();
if (oldHead == null)
return null;
newHead = oldHead.next;
} while (!headNode.compareAndSet(oldHead, newHead));
return oldHead.item;
}
private static class Node<E> {
public final E item;
public Node<E> next;
public Node(E item) {
this.item = item;
}
}
}
執行測試方法:
public static void main(String[] args) {
int loops = 1000;
//分別執行不同的程式數1、2、、4、8、16、32、64...
int threads =1;
TreiberStack<String> stack=new TreiberStack();
ExecutorService pool = Executors.newCachedThreadPool();
for (int j = 0; j < threads; j++) {
pool.submit(new Runnable() {
@Override
public void run() {
for (int i = 0; i < loops; i++) {
stack.push("a");
}
}
});
}
pool.shutdown();
try {
pool.awaitTermination(1, TimeUnit.HOURS);
} catch (InterruptedException e) {
}
System.out.println("希望執行次數:"+ loops*threads +";希望執行次數:"+ stack.adder.longValue());
}
執行結果如下:
threads/times | 希望執行次數 | 實際執行次數 |
---|---|---|
1 | 1000 | 1000 |
2 | 2000 | 2000 |
4 | 4000 | 4038 |
8 | 8000 | 8334 |
16 | 16000 | 16390 |
32 | 32000 | 32688 |
64 | 64000 | 65115 |
128 | 128000 | 138662 |
256 | 256000 | 286673 |
512 | 512000 | 898106 |
通過結果我們可以發現,隨著執行緒數增多,實際執行結果數越來越多,說明衝突增多重試次數增多。
結果
通過“提出假設——驗證假設——證明假設”這一過程,我們確定Synchronized方式和CAS方式在競爭較少的時候效能相差不大,後者略優於前者,而隨著衝突加劇,後者效能較前者顯著下降。
如果你親自執行文中測試方法,你還會發現一個現象,無論是TreiberStack
類的執行時間還是實際執行次數,在同一執行緒數下每次執行結果差別較大,而SynchronizedStack
類的結果較穩定,可見CAS方式執行的隨機性比較大,而Synchronized方式相對穩定。
相關文章
- Java多執行緒實現方式Java執行緒
- Java多執行緒之CASJava執行緒
- java執行緒實現方式Java執行緒
- java多執行緒之(synchronized)Java執行緒synchronized
- Java多執行緒之執行緒同步【synchronized、Lock、volatitle】Java執行緒synchronized
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- Java之實現多執行緒的方式三:實現Callable介面(結合執行緒池使用)Java執行緒
- Java實現多執行緒的三種方式Java執行緒
- Java之實現多執行緒的方式二:實現Runnable介面Java執行緒
- Java中實現執行緒的方式Java執行緒
- Java建立多執行緒的幾種方式實現Java執行緒
- Java多執行緒之synchronized詳解Java執行緒synchronized
- Java多執行緒之synchronized理論Java執行緒synchronized
- java多執行緒3:synchronizedJava執行緒synchronized
- Java多執行緒(三):SynchronizedJava執行緒synchronized
- 【java】【多執行緒】建立執行緒的兩種常用方式(2)Java執行緒
- 面試-實現多執行緒的方式面試執行緒
- 多執行緒系列之 執行緒安全執行緒
- iOS 多執行緒之執行緒安全iOS執行緒
- Java之實現多執行緒的方式一 :繼承Thread類Java執行緒繼承thread
- Java多執行緒之執行緒中止Java執行緒
- 【Java多執行緒】執行緒安全的集合Java執行緒
- Java多執行緒之守護執行緒實戰Java執行緒
- Map實現執行緒安全的3種方式執行緒
- Java多執行緒之二(Synchronized)Java執行緒synchronized
- java synchronized 保護執行緒安全Javasynchronized執行緒
- 多執行緒基礎之synchronized和volatile執行緒synchronized
- Java 多執行緒基礎(四)執行緒安全Java執行緒
- Java之執行緒安全問題的3種處理方式(通過執行緒同步)Java執行緒
- Java建立多執行緒的四種方式Java執行緒
- 最直白的方式直面Java多執行緒Java執行緒
- Java實現多執行緒詳解一 ( 繼承Thread方式 )Java執行緒繼承thread
- 【多執行緒高併發程式設計】二 實現多執行緒的幾種方式執行緒程式設計
- java多執行緒之執行緒的基本使用Java執行緒
- 關於多執行緒的兩種實現方式執行緒
- 多執行緒:繼承方式和實現方式的聯絡與區別執行緒繼承
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——synchronized的使用示例Java執行緒synchronized