1. 程式人生 > >多執行緒中的鎖

多執行緒中的鎖

樂觀鎖和悲觀鎖

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

  • 悲觀鎖指對資料被外界修改持保守的態度,認為資料很容易就會被其他執行緒修改,所以在資料被處理前就先對資料加鎖,並在整個資料處理過程中,使資料處於鎖定狀態,悲觀鎖的實現往往依靠資料庫提供的鎖機制,即在資料庫中,在對資料記錄操作前給記錄加排它鎖,如果獲取鎖失敗,則說明資料正在被其他執行緒修改,當前執行緒則等待或者丟擲異常,如果獲取鎖成功,則對記錄進行操作,然後提交事務後釋放排它鎖
  • 樂觀鎖是相對於悲觀鎖來說的,它認為資料在一般情況下不會造成衝突,所以在訪問記錄前不會加派它鎖,而是在進行資料提交更新時,才會正式對資料衝突與否進行檢測,具體來說,根據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時間白白浪費了。