摘要:前言在上一篇文章中多線程奇幻之旅算法實現(xiàn)線程安全,我們介紹了和方式實現(xiàn)線程安全類的方法,兩種方式一個是鎖定阻塞方式,一個是非阻塞方式。
前言
在上一篇文章中《Java多線程奇幻之旅——CAS算法實現(xiàn)線程安全》,我們介紹了Synchronized和CAS方式實現(xiàn)線程安全類的方法,兩種方式一個是鎖定阻塞方式,一個是非阻塞方式。本文專注于兩種實現(xiàn)方式效率問題。本文是上篇文章的延續(xù),會借用到上文中的代碼,如果沒有閱讀前文請先前往閱讀。
旅程開始 1.大膽假設(shè)在設(shè)計試驗方法之前,針對Synchronized和CAS兩種方式的特點,我們先來思考一下兩種方式效率如何?
首先,我們在回顧一下兩種方式是如何保證線程安全的。Synchronized方式通過大家應(yīng)該很熟悉,他的行為非常悲觀,只要有一個線程進(jìn)入Synchronized臨界區(qū)域(確保不被多線程并發(fā)訪問的區(qū)域),其他線程均不能進(jìn)入,直到早先進(jìn)入的線程退出臨界區(qū)域。和Synchronized相比CAS算法則顯得樂觀多了,他不限制其他線程進(jìn)入臨界區(qū)域,但是當(dāng)一個線程退出臨界區(qū)域的時候,他必須檢查臨界區(qū)域內(nèi)數(shù)據(jù)是否被其他線程修改,一旦被修改,此線程就要做重試操作。
我們舉一個生活化的例子加深理解:
我們把線程比作在馬路上行駛的汽車,臨界區(qū)比作道路交叉的十字路口。
如果所有馬路上只有一輛車(單線程情況),那么我們無需任何處理。如果馬路上不只一輛車要通過十字路口(多線程情況),并且我們不允許車輛在十字路口相撞(線程沖突情況),那么我們必須需要做出一些限制來避免同時通過十字路口的車輛相互碰撞(保證線程安全)。Synchronized方式相當(dāng)于在路口設(shè)置紅綠燈,用“紅燈停,綠燈行”的基本原則限制兩側(cè)路口的汽車同時進(jìn)入十字路口。而CAS方式就要評司機(jī)自覺了,一旦一輛汽車進(jìn)入十字路口后發(fā)現(xiàn)已經(jīng)有另一輛汽車進(jìn)入十字路口,他需要退出十字路口重新進(jìn)入。
我們用生活經(jīng)驗想象一下兩種方式的車輛通行效率,我們經(jīng)??吹皆谲嚵鞑桓叩穆房谄嚢装椎却t綠燈,顯然在車輛比較少的路口設(shè)置紅綠燈很有可能影響通行效率,所有晚上一旦車流下降,某些路口紅綠燈會關(guān)閉以調(diào)高通過效率。我們也看到在某個高峰時段由于路口紅綠燈損壞造成的車輛擁堵,這說明在車流量較多的情況下,紅綠燈的使用恰恰能避免擁堵發(fā)生。
通過紅綠燈的例子我們可以假設(shè),當(dāng)線程競爭比較少的情況下,CAS算法效率較高,反之,Synchronized方式效率較高。
借用上文中兩種“?!钡拇a,構(gòu)建測試方法:
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; //分別運(yùn)行不同的進(jìn)程數(shù)1、2、、4、8、16、32、64... int threads =1; //分別運(yùn)行不同的Stack類。 //SynchronizedStackstack = new SynchronizedStack(); TreiberStack 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); }
設(shè)置不同的threads的值并切換SynchronizedStack類或者TreiberStack類后運(yùn)行結(jié)果如下:
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 |
在線程數(shù)較少,競爭較少的情況下TreiberStack和SynchronizedStack運(yùn)行結(jié)果差距很小,但是隨著線程數(shù)的增多,競爭加劇,TreiberStack較SynchronizedStack執(zhí)行時間明顯延長。
為什么在線程數(shù)較少的情況下TreiberStack和SynchronizedStack沒有明顯差別?
在JDK1.6以后對synchronized關(guān)鍵字做了優(yōu)化,導(dǎo)致加鎖的效率提升,所以和非阻塞方式相比效率也不會相差很多。
為什么在線程數(shù)較多的情況下TreiberStack和SynchronizedStack差別越來越大?
主要原因在于TreiberStack在高并發(fā)的情況下會產(chǎn)生大量的競爭,造成大量重試操作。
我們改造一下TreiberStack類,演示這種情況:
public class TreiberStack{ private AtomicReference > headNode = new AtomicReference<>(); //記錄實際執(zhí)行次數(shù) public static final LongAdder adder=new LongAdder(); public void push(E item) { Node newHead = new Node<>(item); Node oldHead; do { adder.increment(); oldHead = headNode.get(); newHead.next = oldHead; } while (!headNode.compareAndSet(oldHead, newHead)); } public E pop() { Node oldHead; Node 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 { public final E item; public Node next; public Node(E item) { this.item = item; } } }
運(yùn)行測試方法:
public static void main(String[] args) { int loops = 1000; //分別運(yùn)行不同的進(jìn)程數(shù)1、2、、4、8、16、32、64... int threads =1; TreiberStackstack=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("希望執(zhí)行次數(shù):"+ loops*threads +";希望執(zhí)行次數(shù):"+ stack.adder.longValue()); }
執(zhí)行結(jié)果如下:
threads/times | 希望執(zhí)行次數(shù) | 實際執(zhí)行次數(shù) |
---|---|---|
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 |
通過結(jié)果我們可以發(fā)現(xiàn),隨著線程數(shù)增多,實際執(zhí)行結(jié)果數(shù)越來越多,說明沖突增多重試次數(shù)增多。
后記通過“提出假設(shè)——驗證假設(shè)——證明假設(shè)”這一過程,我們確定Synchronized方式和CAS方式在競爭較少的時候性能相差不大,后者略優(yōu)于前者,而隨著沖突加劇,后者性能較前者顯著下降。
如果你親自運(yùn)行文中測試方法,你還會發(fā)現(xiàn)一個現(xiàn)象,無論是TreiberStack類的運(yùn)行時間還是實際執(zhí)行次數(shù),在同一線程數(shù)下每次運(yùn)行結(jié)果差別較大,而SynchronizedStack類的結(jié)果較穩(wěn)定,可見CAS方式執(zhí)行的隨機(jī)性比較大,而Synchronized方式相對穩(wěn)定。
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/68994.html
摘要:大多數(shù)保證線程安全的方法是添加各種類型鎖,使用各種同步機(jī)制,用限制對共享的可變的類變量并發(fā)訪問的方式來保證線程安全。只有保證這兩條語句及中間語句以原子方式執(zhí)行,才能避免多線程覆蓋問題。 前言 對于線程安全,我們有說不盡的話題。大多數(shù)保證線程安全的方法是添加各種類型鎖,使用各種同步機(jī)制,用限制對共享的、可變的類變量并發(fā)訪問的方式來保證線程安全。文本從另一個角度,使用比較交換算法(Comp...
摘要:例子先來看下面的示例來驗證下到底是不是線程安全的。上面的例子我們期望的結(jié)果應(yīng)該是,但運(yùn)行遍,你會發(fā)現(xiàn)總是不為,至少你現(xiàn)在知道了操作它不是線程安全的了。它的性能比較好也是因為避免了使線程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)。 例子 先來看下面的示例來驗證下 i++ 到底是不是線程安全的。 1000個線程,每個線程對共享變量 count 進(jìn)行 1000 次 ++ 操作。 showImg(https://s...
摘要:方法由兩個參數(shù),表示期望的值,表示要給設(shè)置的新值。操作包含三個操作數(shù)內(nèi)存位置預(yù)期原值和新值。如果處的值尚未同時更改,則操作成功。中就使用了這樣的操作。上面操作還有一點是將事務(wù)范圍縮小了,也提升了系統(tǒng)并發(fā)處理的性能。 這是java高并發(fā)系列第21篇文章。 本文主要內(nèi)容 從網(wǎng)站計數(shù)器實現(xiàn)中一步步引出CAS操作 介紹java中的CAS及CAS可能存在的問題 悲觀鎖和樂觀鎖的一些介紹及數(shù)據(jù)庫...
摘要:前言學(xué)習(xí)情況記錄時間子目標(biāo)多線程記錄在學(xué)習(xí)線程安全知識點中,關(guān)于的有關(guān)知識點。對于資源競爭嚴(yán)重線程沖突嚴(yán)重的情況,自旋的概率會比較大,從而浪費更多的資源,效率低于。 前言 學(xué)習(xí)情況記錄 時間:week 1 SMART子目標(biāo) :Java 多線程 記錄在學(xué)習(xí)線程安全知識點中,關(guān)于CAS的有關(guān)知識點。 線程安全是指:多個線程不管以何種方式訪問某個類,并且在主調(diào)代碼中不需要進(jìn)行同步,都能表...
閱讀 3567·2021-11-25 09:43
閱讀 3145·2021-10-08 10:04
閱讀 1635·2019-08-26 12:20
閱讀 2067·2019-08-26 12:09
閱讀 608·2019-08-23 18:25
閱讀 3581·2019-08-23 17:54
閱讀 2337·2019-08-23 17:50
閱讀 813·2019-08-23 14:33