(3)Java併發編程基礎篇

樂觀鎖與悲觀鎖

樂觀鎖和悲觀鎖是在數據庫中引入的名詞,在java的併發包鎖中也有類似的概念所以這邊我們也有必要提及以下。

  • 悲觀鎖

悲觀鎖指在外界對數據進行修改的時候,它都持悲觀的態度,認爲數據都會被其他人進行修改,所以在獲取、修改記錄之前都會對記錄進行加鎖操作。下面看一個典型的例子:

public void updateAction(Integer id){
## 開啓事務
line1 : TablePO tPO= getEntity("select * from table where id = #{id} for update;");
line2 : tPO.setXXX1("");
line3 : tPO.setXXX2("");
line4 : updateEntity("update set xxx1 = #{xxx1} AND xxx2=#{} where id = #{id}");
## 提交事務
}

當多個線程同時調用udpateAction時並同時傳入相同的id,只有一個線程會成功執行line1代碼,其他的線程將會阻塞,這個是因爲同一時間內只能有一個線程能夠獲取對應記錄的鎖,直到這個線程成功的提交事務,釋放鎖之後其他線程纔會被喚醒繼續執行。

  • 樂觀鎖

樂觀鎖相對於悲觀鎖來說它認爲一般情況下不會造成數據的衝突,只有在數據提交更新的時候,纔會正式去檢測數據是否衝突。具體來說是根據update 影響的行數來判定數據是否衝突。下面看一個典型的例子:

public void updateAction(Integer id){
## 開啓事務
line1 : TablePO tPO= getEntity("select ID,VERSION,xxx from table where id = #{id}");
line2 : tPO.setXXX1("");
line3 : tPO.setXXX2("");
line4 : int affectRows = updateEntity("update set xxx1 = #{xxx1} AND version=version+1 where id = #{id} AND version=#{version}");
line5 : if(affectRows != 1 ){
		//.....
		}
## 提交事務

假設線程1 和 線程 2 在t1 時刻同時傳入相同的ID,都會成功執行line1 的代碼,獲取相同的tPO(version=1),t2 時刻線程1 對 tPO進行屬性賦值的操作並 根據條件id = #{id} AND version=#{version} 成功執行 line4 的更新操作,將version更改爲2,t3 時刻 線程2 也對tPO進行屬性的賦值 ,執行line4 時失敗了,返回影響行數0,由於對應記錄version值已經被線程1 更新爲2了。這裏就有點類似有CAS的操作了。

注意:

  1. t1 < t2 < t3
  2. 數據庫中的update 語句本身就具有原子性,所以不會存在多個線程同時執行upadte 語句時出現數據不一致的問題。

公平鎖與非公平鎖

根據線程搶佔鎖的機制將鎖分爲了公平鎖和非公平鎖,公平鎖指對於先請求鎖的線程先獲取鎖,即先到先得,而非公鎖鎖沒有這樣的規定,先請求鎖的線程不一定會先獲取到鎖。需要注意的是在沒有公平性需求的情況下,儘量使用非公平鎖,因爲公平鎖會帶來一定的性能開銷。

獨佔鎖和共享鎖

獨佔鎖 任何時候只能有一個線程持有,它是一種悲觀鎖。共享鎖 多個線程能同時持有,它是一種樂觀鎖。

什麼是可重入鎖

我們知道當一個線程要獲取一個已經被其他線程持有的獨佔鎖時,會被阻塞掛起。那麼當一個線程要再次獲取已經被它自身線程所持有的鎖時會不會被阻塞掛起呢?如果不會那麼就稱它爲可重入鎖。

自旋鎖

自旋鎖指當線程要獲取已經被其他線程已經持有的鎖時,它並不阻塞掛起,而爲進行多次重試獲取該鎖。自旋鎖主要通過消耗CPU的時間來換取線程切換和調度的開銷。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章