天天看點

CAS非鎖實作單例的一個缺陷

第一時間擷取技術幹貨和業界資訊!

CAS非鎖實作單例的一個缺陷

☞ 免費CSDN資料幫下服務 | 免費加群 ☜

最近又是一年新春的面試季,有人說這是金三銀四。但是說到面試,并發和鎖肯定是少不了的。關于并發可以通路我的這篇文章:極客時間《Java并發程式設計實戰》購買返現24,今天我們要說的是,無鎖實作單例模式,以及這種 CAS 實作的單例的缺點。

傳統的 7 種單例模式大緻如下:

CAS非鎖實作單例的一個缺陷

它們都是用鎖來實作。但是如果在面試過程中面試官問你如何使用非鎖來實作一個單例呢?

答案就是下圖這種實作。

CAS非鎖實作單例的一個缺陷

這是網上一位大牛的實作,他的這種非鎖 CAS 實作的單例,挺好的。但是平時可能沒有人使用,比用鎖稍微複雜了一點,這也是為什麼沒有被列入單例模式的 7 大寫法之中了。我在他的基礎上,也就是他的構造方法裡添加了兩行代碼。

CAS非鎖實作單例的一個缺陷

我主要是想看看它到底是執行個體化了幾次。加上這兩行代碼,可以友善我觀察控制台,和統計執行個體化的總次數。

然後,我的測試代碼如下:

CAS非鎖實作單例的一個缺陷

關于 CountDownLatch 有不會的,可以看我的《CountDownLatch 壓測教程》一文。

我這裡主要是想壓測一下,非鎖 CAS 單例模式是否會建立多次對象。

運作上面的 main 方法,我截圖了一下最終結果。

CAS非鎖實作單例的一個缺陷

結論:CAS 以原子方式更新記憶體中相應的值,進而保證了多線程環境下共享變量更新操作的同步。的确,這種方式可以保證每次調用getInstance() 方法得到的一定是同一個執行個體。是以,從功能實作的角度來看,這種做法達到了預期的目的。但是,經過分析和測試,卻發現這種方式有一些預期之外的弊病:可能會建立不止一個對象。

CAS 本身的操作的确是原子方式,但是包裝 CAS 指令的方法并非是全程同步的,當然,在包含 CAS 指令的方法開始調用之前,參數計算過程中更不是互斥執行的!當一個線程測試 instance.get() == null 得到 true 之後,往下它就一定會調用 new Singleton()。因為,這并不是 CAS 方法的一部分,而是它的參數。在調用一個方法之前,需要先将其參數壓入棧,當然,需要先計算參數表達式,是以,産生如上結果也就不難預料了。

CAS 與鎖的差別在于,它是非阻塞的,也就是說,它不會去等待一個條件,而是一定會去執行,結果要麼成功,要麼失敗。它的操作時間是可預期的。如果我們的目的是一定要成功執行 CAS,那就需要不斷循環執行直至成功,同時,建立在成功預期之上大量的準備工作是值得的,但是,如果我們不希望操作一定成功,那為成功操作而做的準備工作就浪費掉了。