1. 程式人生 > >【高併發】面試官:Java中提供了synchronized,為什麼還要提供Lock呢?

【高併發】面試官:Java中提供了synchronized,為什麼還要提供Lock呢?

## 寫在前面 > 在Java中提供了synchronized關鍵字來保證只有一個執行緒能夠訪問同步程式碼塊。既然已經提供了synchronized關鍵字,那為何在Java的SDK包中,還會提供Lock介面呢?這是不是重複造輪子,多此一舉呢?今天,我們就一起來探討下這個問題。 ## 再造輪子? 既然JVM中提供了synchronized關鍵字來保證只有一個執行緒能夠訪問同步程式碼塊,為何還要提供Lock介面呢?這是在重複造輪子嗎?Java的設計者們為何要這樣做呢?讓我們一起帶著疑問往下看。 ## 為何提供Lock介面? 很多小夥伴可能會聽說過,在Java 1.5版本中,synchronized的效能不如Lock,但在Java 1.6版本之後,synchronized做了很多優化,效能提升了不少。那既然synchronized關鍵字的效能已經提升了,那為何還要使用Lock呢? ![](https://img-blog.csdnimg.cn/20200916002540380.jpg) 如果我們向更深層次思考的話,就不難想到了:我們使用synchronized加鎖是無法主動釋放鎖的,這就會涉及到死鎖的問題。 ## 死鎖問題 如果要發生死鎖,則必須存在以下四個必要條件,四者缺一不可。 ![](https://img-blog.csdnimg.cn/20200916002614352.jpg) - **互斥條件** **在一段時間內某資源僅為一個執行緒所佔有。此時若有其他執行緒請求該資源,則請求執行緒只能等待。** - **不可剝奪條件** 執行緒所獲得的資源在未使用完畢之前,不能被其他執行緒強行奪走,即只能由獲得該資源的執行緒自己來釋放(只能是主動釋放)。 - **請求與保持條件** 執行緒已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他執行緒佔有,此時請求執行緒被阻塞,但對自己已獲得的資源保持不放。 - **迴圈等待條件** 在發生死鎖時必然存在一個程序等待佇列{P1,P2,…,Pn},其中P1等待P2佔有的資源,P2等待P3佔有的資源,…,Pn等待P1佔有的資源,形成一個程序等待環路,環路中每一個程序所佔有的資源同時被另一個申請,也就是前一個程序佔有後一個程序所深情地資源。 ## synchronized的侷限性 如果我們的程式使用synchronized關鍵字發生了死鎖時,synchronized關鍵是是無法破壞“不可剝奪”這個死鎖的條件的。這是因為synchronized申請資源的時候, 如果申請不到, 執行緒直接進入阻塞狀態了, 而執行緒進入阻塞狀態, 啥都幹不了, 也釋放不了執行緒已經佔有的資源。 然而,在大部分場景下,我們都是希望“不可剝奪”這個條件能夠被破壞。也就是說對於“不可剝奪”這個條件,佔用部分資源的執行緒進一步申請其他資源時, 如果申請不到, 可以主動釋放它佔有的資源, 這樣不可剝奪這個條件就破壞掉了。 如果我們自己重新設計鎖來解決synchronized的問題,我們該如何設計呢? ## 解決問題 瞭解了synchronized的侷限性之後,如果是讓我們自己實現一把同步鎖,我們該如何設計呢?也就是說,我們在設計鎖的時候,要如何解決synchronized的侷限性問題呢?這裡,我覺得可以從三個方面來思考這個問題。 ![](https://img-blog.csdnimg.cn/20200916002629808.jpg) (1)能夠響應中斷。 synchronized的問題是, 持有鎖A後, 如果嘗試獲取鎖B失敗, 那麼執行緒就進入阻塞狀態, 一旦發生死鎖, 就沒有任何機會來喚醒阻塞的執行緒。 但如果阻塞狀態的執行緒能夠響應中斷訊號, 也就是說當我們給阻塞的執行緒傳送中斷訊號的時候, 能夠喚醒它, 那它就有機會釋放曾經持有的鎖A。 這樣就破壞了不可剝奪條件了。 (2)支援超時。 如果執行緒在一段時間之內沒有獲取到鎖, 不是進入阻塞狀態, 而是返回一個錯誤, 那這個執行緒也有機會釋放曾經持有的鎖。 這樣也能破壞不可剝奪條件。 (3)非阻塞地獲取鎖。 如果嘗試獲取鎖失敗, 並不進入阻塞狀態, 而是直接返回, 那這個執行緒也有機會釋放曾經持有的鎖。 這樣也能破壞不可剝奪條件。 體現在Lock介面上,就是Lock介面提供的三個方法,如下所示。 ```java // 支援中斷的API void lockInterruptibly() throws InterruptedException; // 支援超時的API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支援非阻塞獲取鎖的API boolean tryLock(); ``` * lockInterruptibly() 支援中斷。 - tryLock()方k tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他執行緒獲取),則返回false,也就說這個方法無論如何都會立即返回。在拿不到鎖時不會一直在那等待。 - tryLock(long time, TimeUnit unit)方法 tryLock(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區別在於這個方法在拿不到鎖時會等待一定的時間,在時間期限之內如果還拿不到鎖,就返回false。如果一開始拿到鎖或者在等待期間內拿到了鎖,則返回true。 也就是說,對於死鎖問題,Lock能夠破壞不可剝奪的條件,例如,我們下面的程式程式碼就破壞了死鎖的不可剝奪的條件。 ```java public class TansferAccount{ private Lock thisLock = new ReentrantLock(); private Lock targetLock = new ReentrantLock(); //賬戶的餘額 private Integer balance; //轉賬操作 public void transfer(TansferAccount target, Integer transferMoney){ boolean isThisLock = thisLock.tryLock(); if(isThisLock){ try{ boolean isTargetLock = targetLock.tryLock(); if(isTargetLock){ try{ if(this.balance >= transferMoney){ this.balance -= transferMoney; target.balance += transferMoney; } }finally{ targetLock.unlock } } }finally{ thisLock.unlock(); } } } } ``` 例外,Lock下面有一個ReentrantLock,而ReentrantLock支援公平鎖和非公平鎖。 在使用ReentrantLock的時候, ReentrantLock中有兩個建構函式, 一個是無參建構函式, 一個是傳入fair引數的建構函式。 fair引數代表的是鎖的公平策略, 如果傳入true就表示需要構造一個公平鎖, 反之則表示要構造一個非公平鎖。如下程式碼片段所示。 ```java //無參建構函式: 預設非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } //根據公平策略引數建立鎖 public ReentrantLock(boolean fair){ sync = fair ? new FairSync() : new NonfairSync(); } ``` 鎖的實現在本質上都對應著一個入口等待佇列, 如果一個執行緒沒有獲得鎖, 就會進入等待佇列, 當有執行緒釋放鎖的時候, 就需要從等待佇列中喚醒一個等待的執行緒。 如果是公平鎖, 喚醒的策略就是誰等待的時間長, 就喚醒誰, 很公平; 如果是非公平鎖, 則不提供這個公平保證, 有可能等待時間短的執行緒反而先被喚醒。 而Lock是支援公平鎖的,synchronized不支援公平鎖。 最後,值得注意的是,在使用Lock加鎖時,一定要在finally{}程式碼塊中釋放鎖,例如,下面的程式碼片段所示。 ```java try{ lock.lock(); }finally{ lock.unlock(); } ``` 注:其他synchronized和Lock的詳細說明,小夥伴們自行查閱即可。 ## 重磅福利 關注「 **冰河技術** 」微信公眾號,後臺回覆 “**設計模式**” 關鍵字領取《**深入淺出Java 23種設計模式**》PDF文件。回覆“**Java8**”關鍵字領取《**Java8新特性教程**》PDF文件。回覆“**限流**”關鍵字獲取《**億級流量下的分散式限流解決方案**》PDF文件,三本PDF均是由冰河原創並整理的超硬核教程,面試必備!! **好了,今天就聊到這兒吧!別忘了點個贊,給個在看和轉發,讓更多的人看到,一起學習,一起進步!!** ## 寫在最後 > 如果你覺得冰河寫的還不錯,請微信搜尋並關注「 **冰河技術** 」微信公眾號,跟冰河學習高併發、分散式、微服務、大資料、網際網路和雲原生技術,「 **冰河技術** 」微信公眾號更新了大量技術專題,每一篇技術文章乾貨滿滿!不少讀者已經通過閱讀「 **冰河技術** 」微信公眾號文章,吊打面試官,成功跳槽到大廠;也有不少讀者實現了技術上的飛躍,成為公司的技術骨幹!如果你也想像他們一樣提升自己的能力,實現技術能力的飛躍,進大廠,升職加薪,那就關注「 **冰河技術** 」微信公眾號吧,每天更新超硬核技術乾貨,讓你對如何提升技術能力不再迷茫! ![](https://img-blog.csdnimg.cn/20200906013715