Java:既然有了synchronized,為什么還要提供Lock?

摘要:在Java中提供了synchronized關鍵字來保證只有一個線程能夠訪問同步代碼塊 。既然已經提供了synchronized關鍵字,那為何在Java的SDK包中,還會提供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呢?
如果我們向更深層次思考的話,就不難想到了:我們使用synchronized加鎖是無法主動釋放鎖的,這就會涉及到死鎖的問題 。
死鎖問題如果要發生死鎖,則必須存在以下四個必要條件,四者缺一不可 。
Java:既然有了synchronized,為什么還要提供Lock?

文章插圖
  • 互斥條件
在一段時間內某資源僅為一個線程所占有 。此時若有其他線程請求該資源,則請求線程只能等待 。
  • 不可剝奪條件
線程所獲得的資源在未使用完畢之前,不能被其他線程強行奪走,即只能由獲得該資源的線程自己來釋放(只能是主動釋放) 。
  • 請求與保持條件
線程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他線程占有,此時請求線程被阻塞,但對自己已獲得的資源保持不放 。
  • 循環等待條件
在發生死鎖時必然存在一個進程等待隊列{P1,P2,…,Pn},其中P1等待P2占有的資源,P2等待P3占有的資源,…,Pn等待P1占有的資源,形成一個進程等待環路,環路中每一個進程所占有的資源同時被另一個申請,也就是前一個進程占有后一個進程所深情地資源 。
synchronized的局限性如果我們的程序使用synchronized關鍵字發生了死鎖時,synchronized關鍵是是無法破壞“不可剝奪”這個死鎖的條件的 。這是因為synchronized申請資源的時候,如果申請不到,線程直接進入阻塞狀態了,而線程進入阻塞狀態,啥都干不了,也釋放不了線程已經占有的資源 。
然而,在大部分場景下,我們都是希望“不可剝奪”這個條件能夠被破壞 。也就是說對于“不可剝奪”這個條件,占用部分資源的線程進一步申請其他資源時,如果申請不到,可以主動釋放它占有的資源,這樣不可剝奪這個條件就破壞掉了 。
如果我們自己重新設計鎖來解決synchronized的問題,我們該如何設計呢?
解決問題了解了synchronized的局限性之后,如果是讓我們自己實現一把同步鎖,我們該如何設計呢?也就是說,我們在設計鎖的時候,要如何解決synchronized的局限性問題呢?這里,我覺得可以從三個方面來思考這個問題 。
Java:既然有了synchronized,為什么還要提供Lock?

文章插圖
【Java:既然有了synchronized,為什么還要提供Lock?】(1)能夠響應中斷 。synchronized的問題是,持有鎖A后,如果嘗試獲取鎖B失敗,那么線程就進入阻塞狀態,一旦發生死鎖,就沒有任何機會來喚醒阻塞的線程 。但如果阻塞狀態的線程能夠響應中斷信號,也就是說當我們給阻塞的線程發送中斷信號的時候,能夠喚醒它,那它就有機會釋放曾經持有的鎖A 。這樣就破壞了不可剝奪條件了 。

經驗總結擴展閱讀