為什麼Java有了synchronized之後還造了Lock鎖這個輪子?

三友的java日記 發表於 2022-05-26
Java

眾所周知,synchronized和Lock鎖是java併發變成中兩大利器,可以用來解決執行緒安全的問題。但是為什麼Java有了synchronized之後還是提供了Lock介面這個api,難道僅僅只是重複造了輪子這麼簡單麼?本文就來探討一下這個問題。

談到這個問題,其實很多同學第一反應都會說,Lock鎖的效能比synchronized好,synchronized屬於重量級的鎖。但是在JDK 1.6版本之後,JDK對synchronized進行了一系列效能的優化,synchronized的效能其實有了大大的提升(如果不清楚的同學可以看一下 synchronized真的很重麼?這篇文章,文章內詳細的說明JDK對synchronized做了哪些優化),那麼既然效能不是問題,那麼主要的問題是什麼呢?

synchronized搶佔鎖的特性

我們先來看一下synchronized搶佔鎖的特性。synchronized在搶佔鎖的時候,如果搶佔不到,執行緒直接就進入阻塞狀態了,而執行緒進入阻塞狀態,其實什麼也幹不了,也釋放不了執行緒已經佔有的資源,並且也無法主動或者被動打斷阻塞獲取鎖的操作,只有等別的執行緒釋放鎖之後才會被喚醒來重新獲取鎖。

synchronized阻塞獲取鎖產生的問題

那synchronized這種獲取鎖阻塞的特性,有什麼問題麼?其實有一種很重要的問題,那就是會產生死鎖的問題。

那什麼是死鎖?死鎖是指兩個或者兩個以上的執行緒在執行的過程中,因爭奪資源產生的一種互相等待的現象。

舉個例子來說,執行緒1先對加A加鎖,執行緒2對B加鎖。程式碼執行到某一時刻,執行緒1需要對B加鎖,但是此時B的鎖已經被執行緒2佔有,於是執行緒1就會阻塞,與此同時執行緒2同時也需要對A加鎖,發現A已經被執行緒1持有,也會進入阻塞,於是執行緒1和執行緒2都在等對方釋放資源,就產生了死鎖的問題,並且由於synchronized阻塞的特性,執行緒無法主動或者被動停止阻塞,勢必會導致這個死鎖永遠無法通過主動或者人為干預(其它執行緒干預)來解決。

那麼有什麼好的辦法來解決阻塞導致死鎖的問題呢?

我們分析一下死鎖產生的問題主要是執行緒都在相互等待其它執行緒釋放資源導致的,基於這個問題我們思考一下,如果一個執行緒獲取不到鎖,然後就停止獲取鎖,不阻塞,或者是阻塞一會就不再阻塞,又或是阻塞過程中被其他執行緒打斷,那樣這是不是就不是產生死鎖的問題了。

就拿上面的例子來說,假設執行緒1獲取B的阻塞鎖超過一定時間,主動放棄獲取B的鎖,那麼執行緒1程式碼就可以繼續往下執行,當執行完之後,執行緒1釋放了A鎖,此時執行緒2就能獲取到A的鎖,那麼執行緒2就可以繼續執行了,這樣是不是死鎖的問題就完美解決了。

其實Lock鎖就提供了上述提到的幾種解決方案的api,接下來我們就來看看Lock鎖提供的api。

Lock鎖

 void lockInterruptibly() throws InterruptedException;

阻塞可以被打斷的加鎖方法,這是一個被動放棄獲取鎖的方法。就是說其它執行緒主動當呼叫阻塞執行緒的interrupt方法之後,該阻塞執行緒就會放棄繼續獲取鎖,然後丟擲InterruptedException 異常,所以對於使用方來說,只要捕獲這個異常,就能保證執行緒的程式碼繼續執行了。

boolean tryLock();

這個方法是嘗試加鎖,加鎖失敗後就放棄加鎖,不會阻塞,直接返回false。

 boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

這個方法相比上面的就是嘗試加鎖失敗後在阻塞的一定時間之後,如果還沒有獲取到鎖,那麼就放棄獲取鎖。

Lock介面的實現有很多,但基本上都是基於Java的AQS的實現來完成的。AQS其實主要是維護了一個鎖的狀態欄位state和一個雙向連結串列。當執行緒獲取鎖失敗之後,就會加入到雙向連結串列中,然後阻塞或者不阻塞,這得看具體的方法實現。

Lock介面的一個實現ReentrantLock就是基於AQS實現來講的,這裡就不繼續展開講解ReentrantLock的實現原理,如果有感興趣的同學,可以看一下 一文帶你看懂Java中的Lock鎖底層AQS到底是如何實現的 這篇文章,文章是基於ReentrantLock來講解AQS的加鎖和釋放鎖的原理。

總結

好了,到這裡其實大家應該知道了,為什麼需要Lock鎖,因為synchronized獲取不到鎖的時候會阻塞,並且阻塞不可被打斷的特性會導致可能會產生死鎖的問題,為了解決這個問題,Java就提供了Lock鎖的實現,從主動放棄獲取鎖或者被動放棄獲取鎖的方式,解決一直阻塞可能產生的死鎖問題。

如果覺得這篇文章對你有所幫助,還請幫忙點贊、在看、轉發一下,碼字不易,非常感謝!

如果你想聯絡我,歡迎關注我的個人的微信公眾號三友的java日記,每天都會發布技術性的文章,期待與你一起進步。

掃碼關注公眾號,及時乾貨不錯過。 
為什麼Java有了synchronized之後還造了Lock鎖這個輪子?