1. 程式人生 > >多線程中的鎖

多線程中的鎖

過程 nbsp 講解 ros java 排它 aud 只有一個 再次

樂觀鎖和悲觀鎖

樂觀鎖和悲觀鎖是在數據庫中引入的名詞,但是在並發包鎖裏面也引入了類似的思想,所以這裏還是有必要講解一下。

  • 悲觀鎖指對數據被外界修改持保守的態度,認為數據很容易就會被其他線程修改,所以在數據被處理前就先對數據加鎖,並在整個數據處理過程中,使數據處於鎖定狀態,悲觀鎖的實現往往依靠數據庫提供的鎖機制,即在數據庫中,在對數據記錄操作前給記錄加排它鎖,如果獲取鎖失敗,則說明數據正在被其他線程修改,當前線程則等待或者拋出異常,如果獲取鎖成功,則對記錄進行操作,然後提交事務後釋放排它鎖
  • 樂觀鎖是相對於悲觀鎖來說的,它認為數據在一般情況下不會造成沖突,所以在訪問記錄前不會加派它鎖,而是在進行數據提交更新時,才會正式對數據沖突與否進行檢測,具體來說,根據update返回的行數讓用戶決定如何去做

樂觀鎖並不會使用數據庫提供的鎖機制,一般在表中添加version字段或者使用業務狀態來實現,樂觀鎖直到提交時才鎖定,所以不會產生任何死鎖。

公平鎖和非公平鎖

根據線程獲取鎖的搶占機制,鎖可以分為公平鎖和非公平鎖,公平鎖表示線程獲取鎖的順序是按照線程的請求鎖的時間早晚來決定的,也即是最早請求鎖將最早獲得鎖,而非公平鎖則在運行時間闖入,也就是先來不一定先得。

ReetrantLock提供了公平和非公平鎖的實現

  • 公平鎖: ReetrantLock pairLock=new ReetrantLock(true).
  • 非公平鎖: ReetrantLock pairLock=new ReetrantLock(false),如果構造函數不傳參數,則默認是非公平鎖

在沒有公平性需求的前提下盡量使用非公平鎖,因為公平鎖會帶來性能開銷。

獨占鎖與共享鎖

根據鎖只能被單個線程持有還是能被多個線程共同持有,鎖可以分為獨占鎖和共享鎖。

獨占鎖保證任何時候都只有一個線程能獲得鎖,ReetrantLock就是以獨占方式實現的。共享鎖則可以同時由多個線程持有,例如ReadWriteLock讀寫鎖,它允許一個資源可以被多個線程同時進行讀操作。

獨占鎖是一種悲觀鎖,由於每次訪問資源都先加上互斥鎖,這限制了並發性,因為讀操作不會影響數據的一致性,而獨占鎖只允許在同一個時間由一個線程讀取數據,其他線程必須等待當前線程釋放鎖才能進行讀取。

共享鎖是一種樂觀鎖,它放寬了加鎖的條件,允許多個線程同時進行讀操作

可重入鎖

當一個線程要獲取一個被其他線程持有的獨占鎖時,該線程會阻塞,那麽當一個線程再次獲取它自己已經獲取的鎖時是否會阻塞呢?如果不被阻塞,那麽我們說該鎖是可重入的,也即是只要該線程獲取了鎖,那麽可以無限次數地進入被該鎖鎖住的代碼塊。

實際上,synchronized內部鎖是可重入的,可重入鎖的原理是在鎖內部維護一個線程標示,用來標示該鎖被哪個線程占用,然後關聯一個計數器,一開始計數器值為0,說明該鎖沒有被任何線程紮用,當一個線程獲取了該鎖時,計數器的值會變成1,這時其他線程再來獲取該鎖的時會發現鎖的所有者不是自己而被阻塞掛起,但是當獲取了該鎖的線程再次獲取鎖時發現擁有者是自己,就會把計數器的值加1,當釋放鎖的時候計數器的值-1,當計數器的值為0時,鎖裏面的線程標示被重置為null,這個時候被阻塞的線程會被喚醒來競爭獲取該鎖。

自旋鎖

由於Java中的線程是與操作系統中的線程一一對應的,所以當一個線程在獲取鎖失敗後,會被切換到內核狀態而被掛起,當該線程獲取到鎖時又需要將其切換到內核狀態而喚醒該線程,而從用戶狀態切換到內核狀態的開銷是比較大的,在一定程度上會影響並非性能,自旋鎖則是,當前線程在獲取鎖時,如果發現鎖已經被其他線程占有,它不是馬上阻塞自己,在不放棄CPU使用權的前提下,多次嘗試獲取(默認是10此,可以使用-XX:PreBlockSpinsh參數設置該值),很有可能在後面的幾次嘗試中其他線程已經釋放了鎖,如果嘗試指定的次數後仍沒有獲取到鎖則當前線程才會被阻塞掛起,由此看來自旋鎖是使用CPU時間換取線程阻塞與調度的開銷,但是很有可能這些CPU時間白白浪費了。

多線程中的鎖