Java并发编程实战~Lock
再造管程的理由
synchronized導致死鎖問題,提出了一個破壞不可搶占條件方案,但是這個方案 synchronized 沒有辦法解決。原因是 synchronized 申請資源的時候,如果申請不到,線程直接進入阻塞狀態了,而線程進入阻塞狀態,啥都干不了,也釋放不了線程已經占有的資源。但我們希望的是:
對于“不可搶占”這個條件,占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源,這樣不可搶占這個條件就破壞掉了。如果我們重新設計一把互斥鎖去解決這個問題,那該怎么設計呢?我覺得有三種方案。
1、能夠響應中斷。synchronized 的問題是,持有鎖 A 后,如果嘗試獲取鎖 B 失敗,那么線程就進入阻塞狀態,一旦發生死鎖,就沒有任何機會來喚醒阻塞的線程。但如果阻塞狀態的線程能夠響應中斷信號,也就是說當我們給阻塞的線程發送中斷信號的時候,能夠喚醒它,那它就有機會釋放曾經持有的鎖 A。這樣就破壞了不可搶占條件了。
2、支持超時。如果線程在一段時間之內沒有獲取到鎖,不是進入阻塞狀態,而是返回一個錯誤,那這個線程也有機會釋放曾經持有的鎖。這樣也能破壞不可搶占條件。
3、非阻塞地獲取鎖。如果嘗試獲取鎖失敗,并不進入阻塞狀態,而是直接返回,那這個線程也有機會釋放曾經持有的鎖。這樣也能破壞不可搶占條件。
這三種方案可以全面彌補 synchronized 的問題。到這里相信你應該也能理解了,這三個方案就是“重復造輪子”的主要原因,體現在 API 上,就是 Lock 接口的三個方法。詳情如下:
如何保證可見性
class X {private final Lock rtl = new ReentrantLock();int value;public void addOne() {// 獲取鎖rtl.lock(); try {value+=1;} finally {// 保證鎖能釋放rtl.unlock();}} }ReentrantLock,內部持有一個 volatile 的成員變量 state,獲取鎖的時候,會讀寫 state 的值;解鎖的時候,也會讀寫 state 的值(簡化后的代碼如下面所示)。也就是說,在執行 value+=1 之前,程序先讀寫了一次 volatile 變量 state,在執行 value+=1 之后,又讀寫了一次 volatile 變量 state。根據相關的 Happens-Before 規則:
1、順序性規則:對于線程 T1,value+=1 Happens-Before 釋放鎖的操作 unlock();
2、volatile 變量規則:由于 state = 1 會先讀取 state,所以線程 T1 的 unlock() 操作 Happens-Before 線程 T2 的 lock() 操作;
3、傳遞性規則:線程 T1 的 value+=1 Happens-Before 線程 T2 的 lock() 操作。
什么是可重入鎖
所謂可重入鎖,顧名思義,指的是線程可以重復獲取同一把鎖
除了可重入鎖,可能你還聽說過可重入函數,可重入函數怎么理解呢?指的是線程可以重復調用?顯然不是,所謂可重入函數,指的是多個線程可以同時調用該函數,每個線程都能得到正確結果;同時在一個線程內支持線程切換,無論被切換多少次,結果都是正確的。多線程可以同時執行,還支持線程切換,這意味著什么呢?線程安全啊。所以,可重入函數是線程安全的。
class X {private final Lock rtl = new ReentrantLock();int value;public int get() {// 獲取鎖rtl.lock(); try {return value;} finally {// 保證鎖能釋放rtl.unlock();}}public void addOne() {// 獲取鎖rtl.lock(); try {value = 1 + get(); } finally {// 保證鎖能釋放rtl.unlock();}} }公平鎖與非公平鎖
在使用 ReentrantLock 的時候,你會發現 ReentrantLock 這個類有兩個構造函數,一個是無參構造函數,一個是傳入 fair 參數的構造函數。fair 參數代表的是鎖的公平策略,如果傳入 true 就表示需要構造一個公平鎖,反之則表示要構造一個非公平鎖
// 無參構造函數:默認非公平鎖 public ReentrantLock() {sync = new NonfairSync(); } // 根據公平策略參數創建鎖 public ReentrantLock(boolean fair){sync = fair ? new FairSync() : new NonfairSync(); }鎖都對應著一個等待隊列,如果一個線程沒有獲得鎖,就會進入等待隊列,當有線程釋放鎖的時候,就需要從等待隊列中喚醒一個等待的線程。如果是公平鎖,喚醒的策略就是誰等待的時間長,就喚醒誰,很公平;如果是非公平鎖,則不提供這個公平保證,有可能等待時間短的線程反而先被喚醒。
總結
以上是生活随笔為你收集整理的Java并发编程实战~Lock的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Data JPA 从入门到
- 下一篇: Effective Java~26. 不