在上一篇文章中《Java多線程奇幻之旅——CAS算法實現線程安全》,咱們介紹了Synchronized和CAS方式實現線程安全類的方法,兩種方式一個是鎖定阻塞方式,一個是非阻塞方式。本文專一於兩種實現方式效率問題。本文是上篇文章的延續,會借用到上文中的代碼,若是沒有閱讀前文請先前往閱讀。java
在設計試驗方法以前,針對Synchronized和CAS兩種方式的特色,咱們先來思考一下兩種方式效率如何?
首先,咱們在回顧一下兩種方式是如何保證線程安全的。Synchronized方式經過你們應該很熟悉,他的行爲很是悲觀,只要有一個線程進入Synchronized臨界區域(確保不被多線程併發訪問的區域),其餘線程均不能進入,直到早先進入的線程退出臨界區域。和Synchronized相比CAS算法則顯得樂觀多了,他不限制其餘線程進入臨界區域,可是當一個線程退出臨界區域的時候,他必須檢查臨界區域內數據是否被其餘線程修改,一旦被修改,此線程就要作重試操做。算法
咱們舉一個生活化的例子加深理解:
咱們把線程比做在馬路上行駛的汽車,臨界區比做道路交叉的十字路口。
若是全部馬路上只有一輛車(單線程狀況),那麼咱們無需任何處理。若是馬路上不僅一輛車要經過十字路口(多線程狀況),而且咱們不容許車輛在十字路口相撞(線程衝突狀況),那麼咱們必須須要作出一些限制來避免同時經過十字路口的車輛相互碰撞(保證線程安全)。Synchronized方式至關於在路口設置紅綠燈,用「紅燈停,綠燈行」的基本原則限制兩側路口的汽車同時進入十字路口。而CAS方式就要評司機自覺了,一旦一輛汽車進入十字路口後發現已經有另外一輛汽車進入十字路口,他須要退出十字路口從新進入。
咱們用生活經驗想象一下兩種方式的車輛通行效率,咱們常常看到在車流不高的路口汽車白白等待紅綠燈,顯然在車輛比較少的路口設置紅綠燈頗有可能影響通行效率,全部晚上一旦車流降低,某些路口紅綠燈會關閉以調高經過效率。咱們也看到在某個高峯時段因爲路口紅綠燈損壞形成的車輛擁堵,這說明在車流量較多的狀況下,紅綠燈的使用偏偏能避免擁堵發生。
經過紅綠燈的例子咱們能夠假設,當線程競爭比較少的狀況下,CAS算法效率較高,反之,Synchronized方式效率較高。segmentfault
借用上文中兩種「棧」的代碼,構建測試方法:安全
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六、3二、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
關鍵字作了優化,致使加鎖的效率提高,因此和非阻塞方式相比效率也不會相差不少。ide
爲何在線程數較多的狀況下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; } } }
運行測試方法:oop
public static void main(String[] args) { int loops = 1000; //分別運行不一樣的進程數一、二、、四、八、1六、3二、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方式相對穩定。