反轉!面試被問CAS和Synchronized的區(qū)別,我的回答直接漲了5K

寫在前面

相信很多小伙伴對于CAS,Synchronized相關的問題都很糾結,包括UP也是,對于這幾個的區(qū)別也困擾了很久,那么今天就來做一個總結,面試再也不用擔心了。

一、 CAS

何為CAS?

CAS(Compare And Swap )是樂觀鎖的一種實現(xiàn)方式,是一種輕量級鎖。JAVA1.5開始引入了CAS,JUC下很多工具類都是基于CAS。

CAS的實現(xiàn)方式:

CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。當多個線程同時嘗試使用CAS更新一個變量時,任何時候只有一個線程可以更新成功,若更新失敗,線程會重新進入循環(huán)再次進行嘗試。

CAS在Java中的應用:

前面也說了JUC下面很多工具類都用到了CAS。其主要依賴于Unsafe的CAS操作來進行實現(xiàn)。

例如AtomicInteger下的incrementAndGet操作:

接著來看看Unsafe下的getAndAddInt方法:

可以看到Unsafe中在循環(huán)體內先讀取內存中的value值,然后CAS更新,如果CAS更新成功則退出,如果更新失敗,則循環(huán)重試直到更新成功。

CAS帶來的問題

ABA問題

例如說:

一. 線程1查詢值是否為A

二. 線程2查詢值是否為A

三. 線程2使用CAS將值更新為B

四. 線程2查詢值是否為B

五. 線程2使用CAS將值更新為A

六. 線程1使用CAS將值更新為C

線程一線程二交替執(zhí)行。第二步到第五步,線程二將值由A更新為B由更新為A,但線程一并沒有察覺,因此線程一還是可以繼續(xù)執(zhí)行。我們稱這種現(xiàn)象為ABA問題。

解決方法:

使用版本號 (時間戳),在每次在執(zhí)行數據的修改操作時,都會帶上一個版本號,一旦版本號和數據的版本號一致就可以執(zhí)行修改操作并對版本號加一,否則就執(zhí)行失敗。例如AtomicStampedReference就是通過對值加一個戳(stamp)來解決“ABA”問題的。

循環(huán)開銷過大

CAS操作不成功的話,會導致一直自旋,CPU的壓力會很大。例如說Unsafe下的getAndAddInt方法會一直循環(huán),知道成功才會返回。

只能保證一個變量的原子操作

二、synchronized

相比于CAS基于樂觀鎖實現(xiàn),synchronized是基于悲觀鎖的,當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

  • 對于普通同步方法加鎖時,鎖是當前實例對象
  • 對于靜態(tài)同步方法加鎖時,鎖是當前類的Class對象
  • 對于同步方法塊加鎖時,鎖是Synchonized括號里配置的對象

Synchronized的實現(xiàn)方式:

Synchonized是基于進入和退出Monitor對象來實現(xiàn)方法同步和代碼塊同步,但兩者的實現(xiàn)細節(jié)不一樣。Synchronized 用在方法上時,在字節(jié)碼中是通過方法的 ACC_SYNCHRONIZED 標志來實現(xiàn)的。而代碼塊同步則是使用monitorenter和monitorexit指令實現(xiàn)的。

monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個monitor與之關聯(lián),當且一個monitor被持有后,它將處于鎖定狀態(tài)。線程執(zhí)行到monitorenter指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖,當獲得對象的monitor以后,monitor內部的計數器就會自增(初始為0),當同一個線程再次獲得monitor的時候,計數器會再次自增。當同一個線程執(zhí)行monitorexit指令的時候,計數器會進行自減,當計數器為0的時候,monitor就會被釋放,其他線程便可以獲得monitor。

Synchronized的優(yōu)化:

Java SE 1.6為了減少獲得鎖和釋放鎖帶來的性能消耗,引入了“偏向鎖”和“輕量級鎖”,在Java SE 1.6中,鎖一共有4種狀態(tài),級別從低到高依次是:無鎖狀態(tài)、偏向鎖狀態(tài)、輕量級鎖狀態(tài)和重量級鎖狀態(tài)。

偏向鎖:

當一個線程訪問同步塊并獲取鎖時,會在對象頭和棧幀中的鎖記錄里存儲鎖偏向的線程ID,以后該線程在進入和退出同步塊時不需要進行CAS操作來加鎖和解鎖,只需簡單地測試一下對象頭的Mark Word里是否存儲著指向當前線程的偏向鎖。如果測試成功,表示線程已經獲得了鎖。如果測試失敗,則需要再測試一下Mark Word中偏向鎖的標識是否設置成1(表示當前是偏向鎖),如果沒有設置,則使用CAS競爭鎖;如果設置了,則嘗試使用CAS將對象頭的偏向鎖指向當前線程。

輕量級鎖:

線程在執(zhí)行同步塊之前,JVM會先在當前線程的棧楨中創(chuàng)建用于存儲鎖記錄的空間,并將對象頭中的Mark Word復制到鎖記錄中。然后線程嘗試使用CAS將對象頭中的Mark Word替換為指向鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示其他線程競爭鎖,當前線程便嘗試使用自旋來獲取鎖。

重量級鎖:

重量級鎖是依賴對象內部的monitor鎖來實現(xiàn)。當系統(tǒng)檢查到鎖是重量級鎖之后,會把等待想要獲得鎖的線程進行阻塞,被阻塞的線程不會消耗cup。但是阻塞或者喚醒一個線程時,都需要操作系統(tǒng)來幫忙,需要從用戶態(tài)轉換到內核態(tài),而轉換狀態(tài)是需要消耗很多時間。

作者::CSDN博主:今天會是有offfer的一天么

原文鏈接:https://blog.csdn.net/HZGuilty/article/details/105630199

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容