互聯網JAVA面試常問問題(五)
- 2019 年 10 月 6 日
- 筆記
關於JAVA鎖,還有以下我們需要知道的。
Synchronized中的CAS
在面試題(四)中,我們對Synchronized應該有所印象了,它最大的特徵就是在同一時刻只有一個執行緒能夠獲得對象的監視器(monitor),從而進入到同步程式碼塊或者同步方法之中,即表現為互斥性(排它性)。
JAVA1.6之前,在沒有引入輕量級鎖和偏向鎖之前,Synchronized最主要的問題是:在存在執行緒競爭的情況下會出現執行緒阻塞和喚醒鎖帶來的性能問題,因為這是一種互斥同步(阻塞同步)。
此時引入CAS,它並不是簡單的將執行緒掛起,當CAS操作失敗後會進行一定的嘗試,而非進行耗時的掛起喚醒的操作,因此也叫做非阻塞同步。
1 什麼是CAS?
CAS的全稱是Compare And Swap,即比較交換,其演算法核心思想如下
執行函數:CAS(V,E,N)
其包含3個參數
V表示要更新的變數 E表示預期值 N表示新值
使用鎖時,執行緒獲取鎖是一種悲觀鎖策略,即假設每一次執行臨界區程式碼都會產生衝突,所以當前執行緒獲取到鎖的時候同時也會阻塞其他執行緒獲取該鎖。
而CAS操作(又稱為無鎖操作)是一種樂觀鎖策略,它假設所有執行緒訪問共享資源的時候不會出現衝突,,即使出現衝突就重試當前操作直到沒有衝突為止。
2 CAS的應用場景
在J.U.C包中利用CAS實現類有很多,可以說是支撐起整個concurrency包的實現,在Lock實現中會有CAS改變state變數,在atomic包中的實現類也幾乎都是用CAS實現,以後會詳細分別介紹。
3 CAS的問題
ABA問題
因為CAS會檢查舊值有沒有變化,這裡存在這樣一個有意思的問題。比如一個舊值A變為了成B,然後再變成A,剛好在做CAS時檢查發現舊值並沒有變化依然為A,但是實際上的確發生了變化。解決方案可以沿襲資料庫中常用的樂觀鎖方式,添加一個版本號可以解決。原來的變化路徑A->B->A就變成了1A->2B->3C。java這麼優秀的語言,當然在java 1.5後的atomic包中提供了AtomicStampedReference來解決ABA問題,解決思路就是這樣的。
自旋時間過長
使用CAS時非阻塞同步,也就是說不會將執行緒掛起,會自旋(無非就是一個死循環)進行下一次嘗試,如果這裡自旋時間過長對性能是很大的消耗。
在文章結尾,給大家提出這樣一個問題,假設現在開啟10個執行緒,每個執行緒都累加了100000次,如果想得到10*100000=1000000這個結果,如下程式碼有什麼問題呢?歡迎大家後台留言和小強一起討論。
public class SynchronizedTest implements Runnable { private static int count = 0; public static void main(String[] args) { for (int i = 0; i < 10; i++) { Thread thread = new Thread(new SynchronizedTest()); thread.start(); } try { Thread.sleep(500); } catch (InterruptedException e) { } System.out.println("expected 1000000 but result is: " + count); } @Override public void run() { for (int i = 0; i < 100000; i++) count++; } }