Java併發編程總結2——慎用CAS

来源:http://www.cnblogs.com/everSeeker/archive/2016/06/08/5569414.html
-Advertisement-
Play Games

一、CAS和synchronized適用場景 1、對於資源競爭較少的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操作額外浪費消耗cpu資源;而CAS基於硬體實現,不需要進入內核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。 2、對於資源競爭嚴重 ...


一、CAS和synchronized適用場景

1、對於資源競爭較少的情況,使用synchronized同步鎖進行線程阻塞和喚醒切換以及用戶態內核態間的切換操作額外浪費消耗cpu資源;而CAS基於硬體實現,不需要進入內核,不需要切換線程,操作自旋幾率較少,因此可以獲得更高的性能。

2、對於資源競爭嚴重的情況,CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。以java.util.concurrent.atomic包中AtomicInteger類為例,其getAndIncrement()方法實現如下:

public final int getAndIncrement() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return current;
        }
}

如果compareAndSet(current, next)方法成功執行,則直接返回;如果線程競爭激烈,導致compareAndSet(current, next)方法一直不能成功執行,則會一直迴圈等待,直到耗盡cpu分配給該線程的時間片,從而大幅降低效率。

 

二、CAS錯誤的使用場景

 1 public class CASDemo {
 2     private final int THREAD_NUM = 1000;
 3     private final int MAX_VALUE = 20000000;
 4     private AtomicInteger casI = new AtomicInteger(0);
 5     private int syncI = 0;
 6     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令詳解.txt";
 7 
 8     public void casAdd() throws InterruptedException {
 9         long begin = System.currentTimeMillis();
10         Thread[] threads = new Thread[THREAD_NUM];
11         for (int i = 0; i < THREAD_NUM; i++) {
12             threads[i] = new Thread(new Runnable() {
13                 public void run() {
14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                     }
17                 }
18             });
19             threads[i].start();
20         }
21         for (int j = 0; j < THREAD_NUM; j++) {
22             threads[j].join();
23         }
24         System.out.println("CAS costs time: " + (System.currentTimeMillis() - begin));
25     }
26 
27     public void syncAdd() throws InterruptedException {
28         long begin = System.currentTimeMillis();
29         Thread[] threads = new Thread[THREAD_NUM];
30         for (int i = 0; i < THREAD_NUM; i++) {
31             threads[i] = new Thread(new Runnable() {
32                 public void run() {
33                     while (syncI < MAX_VALUE) {
34                         synchronized ("syncI") {
35                             ++syncI;
36                         }
37                     }
38                 }
39             });
40             threads[i].start();
41         }
42         for (int j = 0; j < THREAD_NUM; j++)
43             threads[j].join();
44         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
45     }
46 }

在我的雙核cpu上運行,結果如下:

可見在不同的線程下,採用CAS計算消耗的時間遠多於使用synchronized方式。原因在於第15行

14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                     }

的操作是一個耗時非常少的操作,15行執行完之後會立刻進入迴圈,繼續執行,從而導致線程衝突嚴重。

 

三、改進的CAS使用場景

為瞭解決上述問題,只需要讓每一次迴圈執行的時間變長,即可以大幅減少線程衝突。修改代碼如下:

 1 public class CASDemo {
 2     private final int THREAD_NUM = 1000;
 3     private final int MAX_VALUE = 1000;
 4     private AtomicInteger casI = new AtomicInteger(0);
 5     private int syncI = 0;
 6     private String path = "/Users/pingping/DataCenter/Books/Linux/Linux常用命令詳解.txt";
 7 
 8     public void casAdd2() throws InterruptedException {
 9         long begin = System.currentTimeMillis();
10         Thread[] threads = new Thread[THREAD_NUM];
11         for (int i = 0; i < THREAD_NUM; i++) {
12             threads[i] = new Thread(new Runnable() {
13                 public void run() {
14                     while (casI.get() < MAX_VALUE) {
15                         casI.getAndIncrement();
16                         try (InputStream in = new FileInputStream(new File(path))) {
17                                 while (in.read() != -1);
18                         } catch (IOException e) {
19                             e.printStackTrace();
20                         }
21                     }
22                 }
23             });
24             threads[i].start();
25         }
26         for (int j = 0; j < THREAD_NUM; j++)
27             threads[j].join();
28         System.out.println("CAS Random costs time: " + (System.currentTimeMillis() - begin));
29     }
30 
31     public void syncAdd2() throws InterruptedException {
32         long begin = System.currentTimeMillis();
33         Thread[] threads = new Thread[THREAD_NUM];
34         for (int i = 0; i < THREAD_NUM; i++) {
35             threads[i] = new Thread(new Runnable() {
36                 public void run() {
37                     while (syncI < MAX_VALUE) {
38                         synchronized ("syncI") {
39                             ++syncI;
40                         }
41                         try (InputStream in = new FileInputStream(new File(path))) {
42                             while (in.read() != -1);
43                         } catch (IOException e) {
44                             e.printStackTrace();
45                         }
46                     }
47                 }
48             });
49             threads[i].start();
50         }
51         for (int j = 0; j < THREAD_NUM; j++)
52             threads[j].join();
53         System.out.println("sync costs time: " + (System.currentTimeMillis() - begin));
54     }
55 }

在while迴圈中,增加了一個讀取文件內容的操作,該操作大概需要耗時40ms,從而可以減少線程衝突。測試結果如下:

可見在資源衝突比較小的情況下,採用CAS方式和synchronized同步效率差不多。為什麼CAS相比synchronized沒有獲得更高的性能呢?

 

測試使用的jdk為1.7,而從jdk1.6開始,對鎖的實現引入了大量的優化,如鎖粗化(Lock Coarsening)、鎖消除(Lock Elimination)、輕量級鎖(Lightweight Locking)、偏向鎖(Biased Locking)、適應性自旋(Adaptive Spinning)等技術來減少鎖操作的開銷。而其中自旋鎖的原理,類似於CAS自旋,甚至比CAS自旋更為優化。具體內容請參考 深入JVM鎖機制1-synchronized。

傳送門:http://blog.csdn.net/chen77716/article/details/6618779

 

四、總結

1、使用CAS線上程衝突嚴重時,會大幅降低程式性能;CAS只適合於線程衝突較少的情況使用。

2、synchronized在jdk1.6之後,已經改進優化。synchronized的底層實現主要依靠Lock-Free的隊列,基本思路是自旋後阻塞,競爭切換後繼續競爭鎖,稍微犧牲了公平性,但獲得了高吞吐量。線上程衝突較少的情況下,可以獲得和CAS類似的性能;而線程衝突嚴重的情況下,性能遠高於CAS。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • array_slice和array_splice函數是用在取出數組的一段切片,array_splice還有用新的切片替換原刪除切片位置的功能。類似javascript中的Array.prototype.splice和Array.prototype.slice方法。 ...
  • 這裡記錄一個例子,工廠模式的理論就不扯淡了。 遇到的問題:支付方式有很多種,比如微信支付 支付寶支付 銀聯支付 等等。我們在在實現的時候發現他麽的流程上是相似的,以及每個方式都有大量的個性配置,在實例化時需要載入他們,以及為了清晰的講調用方和實現方進行分離,就有來下麵的小設計。 以銀聯為例,(通過測 ...
  • 使用multiprocessing 使用multiprocessing.dummy 單使用multiprocessing模塊的指的是多進程,使用multiprocessing.dummy則表示使用的是多線程 ...
  • python flask detect browser language No problem. We won't show you that ad again. Why didn't you like it? Uninteresting Misleading Offensive Repetitiv ...
  • TODO ...
  • JSON的用途 使用基於JavaScript的應用程式,其中包括瀏覽器擴展和網站 使用JSON格式序列化和結構化的數據傳輸網路連接 這主要用於伺服器和Web應用程式之間的數據傳輸 Web服務和API採用JSON格式提供公共數據 它可以用來與現代編程語言 JSON的特點 易於讀寫JSON 輕量級的基於 ...
  • 概述 OTP 平臺的容錯性高,是因為它提供了機制來監控所有 processes 的狀態,如果有進程出現異常, 不僅可以及時檢測到錯誤,還可以對 processes 進行重啟等操作。 有了 supervisor,可以有效的提高系統的可用性,一個 supervior 監督一個或多個應用, 同時, sup ...
  • 十進位轉二進位,除2運算 十進位6轉二進位是 110 (註意從右往左寫,使用算式從下往上寫) 二進位轉十進位,乘2過程 二進位110轉十進位 0*2的0次方+1*2的1次方+1*2的2次方=6 對應關係 0 1 0 1 0 1 32 16 8 4 2 1 把1對應的位置的數加起來就可以了,所以是21 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...