程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java並發編程總結2——慎用CAS,java並發慎用cas

Java並發編程總結2——慎用CAS,java並發慎用cas

編輯:JAVA綜合教程

Java並發編程總結2——慎用CAS,java並發慎用cas


一、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。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved