Java并發(fā)編程總結(jié)——慎用CAS詳解
一、CAS和synchronized適用場景
1、對于資源競爭較少的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態(tài)內(nèi)核態(tài)間的切換操作額外浪費消耗cpu資源;而CAS基于硬件實現(xiàn),不需要進入內(nèi)核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。
2、對于資源競爭嚴重的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低于synchronized。以java.util.concurrent.atomic包中AtomicInteger類為例,其getAndIncrement()方法實現(xiàn)如下:
public final int getAndIncrement() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return current; } }
如果compareAndSet(current, next)方法成功執(zhí)行,則直接返回;如果線程競爭激烈,導致compareAndSet(current, next)方法一直不能成功執(zhí)行,則會一直循環(huán)等待,直到耗盡cpu分配給該線程的時間片,從而大幅降低效率。
二、CAS錯誤的使用場景
public class CASDemo { private final int THREAD_NUM = 1000; private final int MAX_VALUE = 20000000; private AtomicInteger casI = new AtomicInteger(0); private int syncI = 0; private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令詳解.txt"; public void casAdd() throws InterruptedException { long begin = System.currentTimeMillis(); Thread[] threads = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { threads[i] = new Thread(new Runnable() { public void run() { while (casI.get() < MAX_VALUE) { casI.getAndIncrement(); } } }); threads[i].start(); } for (int j = 0; j < THREAD_NUM; j++) { threads[j].join(); } System.out.println("CAS costs time: " + (System.currentTimeMillis() - begin)); } public void syncAdd() throws InterruptedException { long begin = System.currentTimeMillis(); Thread[] threads = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { threads[i] = new Thread(new Runnable() { public void run() { while (syncI < MAX_VALUE) { synchronized ("syncI") { ++syncI; } } } }); threads[i].start(); } for (int j = 0; j < THREAD_NUM; j++) threads[j].join(); System.out.println("sync costs time: " + (System.currentTimeMillis() - begin)); } }
在我的雙核cpu上運行,結(jié)果如下:
可見在不同的線程下,采用CAS計算消耗的時間遠多于使用synchronized方式。原因在于第15行
14 while (casI.get() < MAX_VALUE) { 15 casI.getAndIncrement(); 16 }
的操作是一個耗時非常少的操作,15行執(zhí)行完之后會立刻進入循環(huán),繼續(xù)執(zhí)行,從而導致線程沖突嚴重。
三、改進的CAS使用場景
為了解決上述問題,只需要讓每一次循環(huán)執(zhí)行的時間變長,即可以大幅減少線程沖突。修改代碼如下:
public class CASDemo { private final int THREAD_NUM = 1000; private final int MAX_VALUE = 1000; private AtomicInteger casI = new AtomicInteger(0); private int syncI = 0; private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令詳解.txt"; public void casAdd2() throws InterruptedException { long begin = System.currentTimeMillis(); Thread[] threads = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { threads[i] = new Thread(new Runnable() { public void run() { while (casI.get() < MAX_VALUE) { casI.getAndIncrement(); try (InputStream in = new FileInputStream(new File(path))) { while (in.read() != -1); } catch (IOException e) { e.printStackTrace(); } } } }); threads[i].start(); } for (int j = 0; j < THREAD_NUM; j++) threads[j].join(); System.out.println("CAS Random costs time: " + (System.currentTimeMillis() - begin)); } public void syncAdd2() throws InterruptedException { long begin = System.currentTimeMillis(); Thread[] threads = new Thread[THREAD_NUM]; for (int i = 0; i < THREAD_NUM; i++) { threads[i] = new Thread(new Runnable() { public void run() { while (syncI < MAX_VALUE) { synchronized ("syncI") { ++syncI; } try (InputStream in = new FileInputStream(new File(path))) { while (in.read() != -1); } catch (IOException e) { e.printStackTrace(); } } } }); threads[i].start(); } for (int j = 0; j < THREAD_NUM; j++) threads[j].join(); System.out.println("sync costs time: " + (System.currentTimeMillis() - begin)); } }
在while循環(huán)中,增加了一個讀取文件內(nèi)容的操作,該操作大概需要耗時40ms,從而可以減少線程沖突。測試結(jié)果如下:
可見在資源沖突比較小的情況下,采用CAS方式和synchronized同步效率差不多。為什么CAS相比synchronized沒有獲得更高的性能呢?
測試使用的jdk為1.7,而從jdk1.6開始,對鎖的實現(xiàn)引入了大量的優(yōu)化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術(shù)來減少鎖操作的開銷。而其中自旋鎖的原理,類似于CAS自旋,甚至比CAS自旋更為優(yōu)化。具體內(nèi)容請參考 深入JVM鎖機制1-synchronized。
四、總結(jié)
1、使用CAS在線程沖突嚴重時,會大幅降低程序性能;CAS只適合于線程沖突較少的情況使用。
2、synchronized在jdk1.6之后,已經(jīng)改進優(yōu)化。synchronized的底層實現(xiàn)主要依靠Lock-Free的隊列,基本思路是自旋后阻塞,競爭切換后繼續(xù)競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴重的情況下,性能遠高于CAS。
以上這篇Java并發(fā)編程總結(jié)——慎用CAS詳解就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
springboot?使用clickhouse實時大數(shù)據(jù)分析引擎(使用方式)
這篇文章主要介紹了springboot?使用clickhouse實時大數(shù)據(jù)分析引擎的方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧2022-02-02詳解FileInputStream讀取文件數(shù)據(jù)的兩種方式
這篇文章主要介紹了詳解FileInputStream讀取文件數(shù)據(jù)的兩種方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-08-08Redisson RedLock紅鎖加鎖實現(xiàn)過程及原理
本文主要介紹了Redis中Redisson紅鎖(Redlock)使用原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-02-02Java與Scala創(chuàng)建List與Map的實現(xiàn)方式
這篇文章主要介紹了Java與Scala創(chuàng)建List與Map的實現(xiàn)方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10Java中使用裝飾設計模式實現(xiàn)動態(tài)增強對象功能
裝飾設計模式是Java中一種常用的設計模式,它通過動態(tài)地將功能透明地附加到對象上,以擴展對象的功能。裝飾設計模式主要應用于需要動態(tài)、透明地增強對象功能的場景。在Java中,裝飾設計模式可通過繼承、接口和代理等方式實現(xiàn)2023-04-04