Java面試題沖刺第二十四天--并發(fā)編程
面試題1:說一下你對ReentrantLock的理解?
ReentrantLock是JDK1.5引入的,它擁有與synchronized相同的并發(fā)性和內(nèi)存語義,并提供了超出synchonized的其他高級功能(例如,中斷鎖等候、條件變量等),并且使用ReentrantLock比synchronized能獲得更好的可伸縮性。
ReentrantLock主要利用: CAS(compare-and-swap) + AQS(AbstractQueuedSynchronizer)隊列來實現(xiàn)。它支持公平鎖和非公平鎖,兩者的實現(xiàn)類似。
CAS:
CAS(compare-and-swap),見名知意,比較并交換。CAS 加 volatile 關(guān)鍵字是實現(xiàn)并發(fā)包的基石。沒有CAS就不會有并發(fā)包,synchronized是一種獨占鎖、悲觀鎖,java.util.concurrent中借助了CAS指令實現(xiàn)了一種區(qū)別于synchronized的一種樂觀鎖。
CAS引用了樂觀鎖思想,每次拿數(shù)據(jù)的時候都認為別的線程不會修改這個數(shù)據(jù),所以不會上鎖,但是在更新的時候會通過標記參數(shù)判斷一下在此期間(更新期間)別的線程有沒有已經(jīng)修改過該標記數(shù)據(jù),如果發(fā)現(xiàn)有其他線程在修改且未修改完成,并不會像悲觀鎖那樣阻塞線程,而是直接返回,可以去選擇再次重試獲得鎖,也可以直接退出。
舉個流程示例
如CAS操作包括三個操作數(shù):需要讀寫的內(nèi)存位置(V)、預期原值(A)、新值(B)。如果內(nèi)存位置與預期原值的A相匹配,說明在此期間(更新期間)別的線程未修改過該標記數(shù)據(jù),那么將內(nèi)存位置的值更新為新值B。如果內(nèi)存位置與預期原值的值不匹配,那么處理器不會做任何操作。
無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)
AQS:
AQS主要利用硬件原語指令CAS,來實現(xiàn)輕量級多線程同步機制,并且不會引起CPU上文切換和調(diào)度,同時提供內(nèi)存可見性和原子化更新保證(線程安全的三要素:原子性、可見性、順序性)。
AQS的本質(zhì)上是一個同步器/阻塞鎖的基礎框架,其作用主要是提供加鎖、釋放鎖,并在內(nèi)部維護一個FIFO等待隊列,用于存儲由于鎖競爭而阻塞的線程。
追問1:你認為 ReentrantLock 相比 synchronized 都有哪些區(qū)別?
優(yōu)秀問答摘自:https://ask.csdn.net/questions/1101634
兩者的共同點:
- 都是用來協(xié)調(diào)多線程對共享對象、變量的訪問
- 都是可重入鎖,同一線程可以多次獲得同一個鎖
- 都保證了可見性和互斥性
兩者的不同點:
- ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖;
- ReentrantLock 可響應中斷、可輪回,synchronized 是不可以響應中斷的,為處理鎖的不可用性提供了更高的靈活性;
- ReentrantLock 是API 級別的,synchronized 是 JVM 級別的;
- ReentrantLock 可以實現(xiàn)公平鎖;
- ReentrantLock 通過 Condition 可以綁定多個條件;
- 底層實現(xiàn)不一樣, synchronized 是同步阻塞,使用的是悲觀并發(fā)策略,ReentrantLock 是同步非阻塞,采用的是樂觀并發(fā)策略;
- Lock 是一個接口,而 synchronized 是 Java 中的關(guān)鍵字,synchronized 是內(nèi)置的語言實現(xiàn);
- synchronized 在發(fā)生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現(xiàn)象發(fā)生;
- 而 Lock 在發(fā)生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現(xiàn)象,因此使用 Lock 時需要在 finally 塊中釋放鎖。
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
while(條件判斷表達式) {
condition.wait();
}
// 處理邏輯
} finally {
lock.unlock();
}
- Lock 可以讓等待鎖的線程響應中斷,而 synchronized 卻不行,使用 synchronized 時,等待的線程會一直等待下去,不能夠響應中斷。
- 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到。
- Lock 可以提高多個線程進行讀操作的效率,就是實現(xiàn)讀寫鎖等。
面試題2:解釋一下公平鎖和非公平鎖?
ReenTrantLock可以指定是公平鎖還是非公平鎖,而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲鎖。
我們剛才提及到AQS中,如果線程A正處于lock狀態(tài),線程B進來時發(fā)現(xiàn)線程A處于lock狀態(tài),會自動進入阻塞隊列,等待取鎖;這時候當線程C也進來了也發(fā)現(xiàn)線程A處于lock狀態(tài),也會自動進入阻塞隊列。那么等A釋放鎖后,下次加鎖到底是線程B先拿到還是線程C先拿到呢?
ReentrantLock有個構(gòu)造方法用于設置鎖的公平性,如果我們僅僅是new了一個ReentrantLock的話,那么就是非公平鎖(默認),就是靠自己去爭取,完全的隨機性。如果我們在new ReentrantLock(true) 加入 true參數(shù)時,公平鎖,就會遵循先入先出的原則,保證了鎖的公平性。
面試題3:能詳細說一下CAS具體實現(xiàn)原理么?
首先,CAS的英文單詞是Compare and Swap,即是比較并替換。CAS機制中使用了3個基本操作數(shù):內(nèi)存地址V,舊的預期值A,待替換的新值B。
CAS規(guī)則是:當需要更新一個變量的值的時候,只有當變量的預期值A和內(nèi)存地址V中的實際值相同的時候,才會把內(nèi)存地址V對應的值替換成B。
下面我們通過一個例子來講解:
1.在內(nèi)存地址V中存儲的值是陳哈哈

2.線程01想要把變量的值改成僑總,對于線程01而言,內(nèi)存地址 V=‘陳哈哈’,舊的預期值 A=‘陳哈哈’,需要替換的新值 B=‘僑總’。

3.在線程01要提交更新之前,另外一個線程02搶先一步,將內(nèi)存地址V中的值更新成了V='比特幣'。

4.線程01開始提交更新的時候,按照CAS機制,首先進行A的值與內(nèi)存地址V中的值進行比較( A=‘陳哈哈’ V=‘比特幣’),發(fā)現(xiàn) A != V 中的實際值,提交失敗。

5.線程01未獲取鎖后進行重試,重新獲取內(nèi)存地址V的當前值(V=‘比特幣’),并重新賦值想要修改的值(B=‘僑總’)。截至目前,線程01舊的預期值為A='比特幣',B='僑總',這個重新嘗試的過程被稱為自旋。

6.這一次就比較順利了,沒有其他線程改變該變量的值,所以線程01通過CAS機制,比較舊的預期值A與內(nèi)存地址V的值,相同(V == A),可以替換。

7.線程01進行替換,把地址V(V=‘比特幣’)的值替換成B(B=‘僑總’)。

以上就是一個比較完整的CAS鎖沖突的處理方式。
從思想上來看,synchronized屬于悲觀鎖,悲觀的認為程序中的并發(fā)問題十分嚴重,所以嚴防死守,只讓一個線程操作該代碼塊。而CAS屬于樂觀鎖,樂觀地認為程序中的并發(fā)問題并不那么嚴重,所以讓線程不斷的去嘗試更新,在并發(fā)問題不嚴重的時候性能要比synchronized快。
追問1:那CAS的缺陷有哪些呢?
當然,CAS也有缺點,如ABA問題,自旋鎖消耗問題、多變量共享一致性問題等。
1.ABA:
問題描述:
線程t1將它的值從A變?yōu)锽,再從B變?yōu)锳。同時有線程t2要將值從A變?yōu)镃。但CAS檢查的時候會發(fā)現(xiàn)沒有改變,但是實質(zhì)上它已經(jīng)發(fā)生了改變 。可能會造成數(shù)據(jù)的缺失。
解決方法:
CAS還是類似于樂觀鎖,同數(shù)據(jù)樂觀鎖的方式給它加一個版本號或者時間戳,如AtomicStampedReference
2.自旋消耗資源:
問題描述:
多個線程爭奪同一個資源時,如果自旋一直不成功,將會一直占用CPU。
解決方法:
破壞掉for死循環(huán),當超過一定時間或者一定次數(shù)時,return退出。JDK8新增的LongAddr,和ConcurrentHashMap類似的方法。當多個線程競爭時,將粒度變小,將一個變量拆分為多個變量,達到多個線程訪問多個資源的效果,最后再調(diào)用sum把它合起來。
雖然base和cells都是volatile修飾的,但感覺這個sum操作沒有加鎖,可能sum的結(jié)果不是那么精確。
3.多變量共享一致性問題:
解決方法:
CAS操作是針對一個變量的,如果對多個變量操作,
- 可以加鎖來解決。
- 封裝成對象類解決。
追問2:講一下什么是ABA問題?怎么解決?
ABA:如果另一個線程修改V值假設原來是A,先修改成B,再修改回成A。當前線程的CAS操作無法分辨當前V值是否發(fā)生過變化。
舉個例子1:
例一:你和女神一起喝茶,女神喝了一半去廁所了,你猥瑣的喝了她剩下的半杯,然后又從你杯子里倒了半杯給她,女神回來后也不知道是否被人喝過。
如果覺得例子1太猥瑣的話,請看例子2:
例二:
線程1首先執(zhí)行成功,把余額10000更新為5000(取錢線程)。同時線程2由于某種原因陷入了阻塞狀態(tài)(取錢線程)。這時候,(線程3)僑總匯款給了我5000元,執(zhí)行成功,我的賬戶5000更新為10000元。過一會兒,線程2恢復運行,由于之前阻塞的時候獲得了當前值為:10000,并且經(jīng)過compare檢測,此時存款也的確是10000元,所以又成功把變量值從10000更新成了5000。僑總:哈哥,5000給你打過去了!我:查到賬戶只有5000,你他娘的糊弄老子呢??僑總:???我好幾十個比特幣的人,還在乎你這5000塊錢了。。。
今天上午10:30:00:我銀行卡有一萬塊錢,今天我來ATM機取5000塊出來買比特幣,但由于ATM機硬件問題,導致取款操作同時提交了兩遍,后臺開啟了兩個線程(線程1、線程2),兩個線程都是獲取當前值10000元,要更新成5000元;理想情況下,應該一個線程更新成功,一個線程更新失敗,我的存款只扣除一次,也就是余額應為5000元 。
好巧不巧,也是今天上午10:30:00:僑總上次買幣欠我5000塊,經(jīng)過我再三催債,表示再不還錢就把你買幣的事兒告訴你媳婦!也正巧這個點兒,僑總給我轉(zhuǎn)了5000元到卡里(線程3)。沒想到這再正常不過的事兒,缺被ABA問題坑了!我可忍不了!
事情是這樣的:
這就是經(jīng)典的 A → B → A 問題,通過上面的例子,相信同學們也基本了解它的原理了。其實解決方式也很簡單,比如例一,我們將每一次倒水假設有一個自動記錄儀記錄下,這樣主人回來就可以分辨在她離開后是否發(fā)生過重新倒?jié)M的情況。這也是解決ABA問題目前采用的策略。
使用版本號,通過比較值 + 版本號才判斷是否可以替換。這么看來如果要解決ABA問題,就需要在CAS基礎上在增加一個版本號的校驗,當值 + 版本號都相等時,才進行替換,其他部分均不變。
而在Java中,AtomicStampedReference類就實現(xiàn)了用版本號做比較的CAS機制。狗子們,你心里有數(shù)了么?
總結(jié)
本篇文章就到這里了,希望能給你帶來幫助,也希望您關(guān)注腳本之家的更多內(nèi)容!
- Java并發(fā)編程之代碼實現(xiàn)兩玩家交換裝備
- Java并發(fā)編程之阻塞隊列(BlockingQueue)詳解
- java實戰(zhàn)案例之用戶注冊并發(fā)送郵件激活/發(fā)送郵件驗證碼
- JAVA并發(fā)圖解
- java并發(fā)編程JUC CountDownLatch線程同步
- Java并發(fā)之Condition案例詳解
- java并發(fā)編程之ThreadLocal詳解
- Java 處理高并發(fā)負載類優(yōu)化方法案例詳解
- 淺談Java高并發(fā)解決方案以及高負載優(yōu)化方法
- Java httpClient連接池支持多線程高并發(fā)的實現(xiàn)
- Java中常見的并發(fā)控制手段淺析
- Java之Rsync并發(fā)遷移數(shù)據(jù)并校驗詳解
- Java 模擬真正的并發(fā)請求詳情
相關(guān)文章
Canal搭建?idea設置及采集數(shù)據(jù)到kafka的操作方法
這篇文章主要介紹了Canal搭建idea設置及采集數(shù)據(jù)到kafka的相關(guān)知識,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-05-05
java中實體類實現(xiàn)時間日期自動轉(zhuǎn)換方式
這篇文章主要介紹了java中實體類實現(xiàn)時間日期自動轉(zhuǎn)換方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-06-06
springboot 配置DRUID數(shù)據(jù)源的方法實例分析
這篇文章主要介紹了springboot 配置DRUID數(shù)據(jù)源的方法,結(jié)合實例形式分析了springboot 配置阿里DRUID數(shù)據(jù)源的具體步驟與相關(guān)操作技巧,需要的朋友可以參考下2019-12-12

