溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java面試題之并發編程的示例分析

發布時間:2021-08-09 13:54:02 來源:億速云 閱讀:189 作者:小新 欄目:開發技術

小編給大家分享一下Java面試題之并發編程的示例分析,希望大家閱讀完這篇文章之后都有所收獲,下面讓我們一起去探討吧!

面試題1:說一下你對ReentrantLock的理解?

ReentrantLock是JDK1.5引入的,它擁有與synchronized相同的并發性和內存語義,并提供了超出synchonized的其他高級功能(例如,中斷鎖等候、條件變量等),并且使用ReentrantLock比synchronized能獲得更好的可伸縮性。

ReentrantLock主要利用: CAS(compare-and-swap) + AQS(AbstractQueuedSynchronizer)隊列來實現。它支持公平鎖和非公平鎖,兩者的實現類似。

CAS:

CAS(compare-and-swap),見名知意,比較并交換。CAS 加 volatile 關鍵字是實現并發包的基石。沒有CAS就不會有并發包,synchronized是一種獨占鎖、悲觀鎖,java.util.concurrent中借助了CAS指令實現了一種區別于synchronized的一種樂觀鎖。

CAS引用了樂觀鎖思想,每次拿數據的時候都認為別的線程不會修改這個數據,所以不會上鎖,但是在更新的時候會通過標記參數判斷一下在此期間(更新期間)別的線程有沒有已經修改過該標記數據,如果發現有其他線程在修改且未修改完成,并不會像悲觀鎖那樣阻塞線程,而是直接返回,可以去選擇再次重試獲得鎖,也可以直接退出。

舉個流程示例

如CAS操作包括三個操作數:需要讀寫的內存位置(V)、預期原值(A)、新值(B)。如果內存位置與預期原值的A相匹配,說明在此期間(更新期間)別的線程未修改過該標記數據,那么將內存位置的值更新為新值B。如果內存位置與預期原值的值不匹配,那么處理器不會做任何操作。

無論哪種情況,它都會在 CAS 指令之前返回該位置的值。(在 CAS 的一些特殊情況下將僅返回 CAS 是否成功,而不提取當前值。)

AQS:

AQS主要利用硬件原語指令CAS,來實現輕量級多線程同步機制,并且不會引起CPU上文切換和調度,同時提供內存可見性和原子化更新保證(線程安全的三要素:原子性、可見性、順序性)。

AQS的本質上是一個同步器/阻塞鎖的基礎框架,其作用主要是提供加鎖、釋放鎖,并在內部維護一個FIFO等待隊列,用于存儲由于鎖競爭而阻塞的線程。

追問1:你認為 ReentrantLock 相比 synchronized 都有哪些區別?

優秀問答摘自:https://ask.csdn.net/questions/1101634

兩者的共同點:

  • 都是用來協調多線程對共享對象、變量的訪問

  • 都是可重入鎖,同一線程可以多次獲得同一個鎖

  • 都保證了可見性和互斥性

兩者的不同點:

  • ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖;

  • ReentrantLock 可響應中斷、可輪回,synchronized 是不可以響應中斷的,為處理鎖的不可用性提供了更高的靈活性;

  • ReentrantLock 是API 級別的,synchronized 是 JVM 級別的;

  • ReentrantLock 可以實現公平鎖;

  • ReentrantLock 通過 Condition 可以綁定多個條件;

  • 底層實現不一樣, synchronized 是同步阻塞,使用的是悲觀并發策略,ReentrantLock 是同步非阻塞,采用的是樂觀并發策略;

  • Lock 是一個接口,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言實現;

  • synchronized 在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;

  • 而 Lock 在發生異常時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現象,因此使用 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 可以提高多個線程進行讀操作的效率,就是實現讀寫鎖等。

面試題2:解釋一下公平鎖和非公平鎖?

ReenTrantLock可以指定是公平鎖還是非公平鎖,而synchronized只能是非公平鎖。所謂的公平鎖就是先等待的線程先獲鎖。

我們剛才提及到AQS中,如果線程A正處于lock狀態,線程B進來時發現線程A處于lock狀態,會自動進入阻塞隊列,等待取鎖;這時候當線程C也進來了也發現線程A處于lock狀態,也會自動進入阻塞隊列。那么等A釋放鎖后,下次加鎖到底是線程B先拿到還是線程C先拿到呢?

Java面試題之并發編程的示例分析

ReentrantLock有個構造方法用于設置鎖的公平性,如果我們僅僅是new了一個ReentrantLock的話,那么就是非公平鎖(默認),就是靠自己去爭取,完全的隨機性。如果我們在new ReentrantLock(true) 加入 true參數時,公平鎖,就會遵循先入先出的原則,保證了鎖的公平性。

面試題3:能詳細說一下CAS具體實現原理么?

首先,CAS的英文單詞是Compare and Swap,即是比較并替換。CAS機制中使用了3個基本操作數:內存地址V,舊的預期值A,待替換的新值B。

CAS規則是:當需要更新一個變量的值的時候,只有當變量的預期值A和內存地址V中的實際值相同的時候,才會把內存地址V對應的值替換成B。

下面我們通過一個例子來講解:

1.在內存地址V中存儲的值是陳哈哈

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

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

Java面試題之并發編程的示例分析

??以上就是一個比較完整的CAS鎖沖突的處理方式。

??從思想上來看,synchronized屬于悲觀鎖,悲觀的認為程序中的并發問題十分嚴重,所以嚴防死守,只讓一個線程操作該代碼塊。而CAS屬于樂觀鎖,樂觀地認為程序中的并發問題并不那么嚴重,所以讓線程不斷的去嘗試更新,在并發問題不嚴重的時候性能要比synchronized快。

追問1:那CAS的缺陷有哪些呢?

當然,CAS也有缺點,如ABA問題,自旋鎖消耗問題、多變量共享一致性問題等。

1.ABA:

問題描述:

線程t1將它的值從A變為B,再從B變為A。同時有線程t2要將值從A變為C。但CAS檢查的時候會發現沒有改變,但是實質上它已經發生了改變 ??赡軙斐蓴祿娜笔?。

解決方法:

CAS還是類似于樂觀鎖,同數據樂觀鎖的方式給它加一個版本號或者時間戳,如AtomicStampedReference

2.自旋消耗資源:

問題描述:

多個線程爭奪同一個資源時,如果自旋一直不成功,將會一直占用CPU。

解決方法:

破壞掉for死循環,當超過一定時間或者一定次數時,return退出。JDK8新增的LongAddr,和ConcurrentHashMap類似的方法。當多個線程競爭時,將粒度變小,將一個變量拆分為多個變量,達到多個線程訪問多個資源的效果,最后再調用sum把它合起來。

雖然base和cells都是volatile修飾的,但感覺這個sum操作沒有加鎖,可能sum的結果不是那么精確。

3.多變量共享一致性問題:

解決方法:

CAS操作是針對一個變量的,如果對多個變量操作,

  • 可以加鎖來解決。

  • 封裝成對象類解決。

追問2:講一下什么是ABA問題?怎么解決?

ABA:如果另一個線程修改V值假設原來是A,先修改成B,再修改回成A。當前線程的CAS操作無法分辨當前V值是否發生過變化。

舉個例子1:

例一:你和女神一起喝茶,女神喝了一半去廁所了,你猥瑣的喝了她剩下的半杯,然后又從你杯子里倒了半杯給她,女神回來后也不知道是否被人喝過。

如果覺得例子1太猥瑣的話,請看例子2:

例二:

今天上午10:30:00:我銀行卡有一萬塊錢,今天我來ATM機取5000塊出來買比特幣,但由于ATM機硬件問題,導致取款操作同時提交了兩遍,后臺開啟了兩個線程(線程1、線程2),兩個線程都是獲取當前值10000元,要更新成5000元;理想情況下,應該一個線程更新成功,一個線程更新失敗,我的存款只扣除一次,也就是余額應為5000元 。

好巧不巧,也是今天上午10:30:00:僑總上次買幣欠我5000塊,經過我再三催債,表示再不還錢就把你買幣的事兒告訴你媳婦!也正巧這個點兒,僑總給我轉了5000元到卡里(線程3)。沒想到這再正常不過的事兒,缺被ABA問題坑了!我可忍不了!

事情是這樣的:

線程1首先執行成功,把余額10000更新為5000(取錢線程)。同時線程2由于某種原因陷入了阻塞狀態(取錢線程)。這時候,(線程3)僑總匯款給了我5000元,執行成功,我的賬戶5000更新為10000元。過一會兒,線程2恢復運行,由于之前阻塞的時候獲得了當前值為:10000,并且經過compare檢測,此時存款也的確是10000元,所以又成功把變量值從10000更新成了5000。僑總:哈哥,5000給你打過去了!我:查到賬戶只有5000,你他娘的糊弄老子呢??僑總:???我好幾十個比特幣的人,還在乎你這5000塊錢了。。。

這就是經典的 A → B → A 問題,通過上面的例子,相信同學們也基本了解它的原理了。其實解決方式也很簡單,比如例一,我們將每一次倒水假設有一個自動記錄儀記錄下,這樣主人回來就可以分辨在她離開后是否發生過重新倒滿的情況。這也是解決ABA問題目前采用的策略。

使用版本號,通過比較值 + 版本號才判斷是否可以替換。這么看來如果要解決ABA問題,就需要在CAS基礎上在增加一個版本號的校驗,當值 + 版本號都相等時,才進行替換,其他部分均不變。

而在Java中,AtomicStampedReference類就實現了用版本號做比較的CAS機制。

看完了這篇文章,相信你對“Java面試題之并發編程的示例分析”有了一定的了解,如果想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女