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