Java中提供synchronized后為什么還要提供Lock
摘要: 在Java中提供了synchronized關(guān)鍵字來保證只有一個(gè)線程能夠訪問同步代碼塊。既然已經(jīng)提供了synchronized關(guān)鍵字,那為何在Java的SDK包中,還會(huì)提供Lock接口呢?這是不是重復(fù)造輪子,多此一舉呢?今天,我們就一起來探討下這個(gè)問題。
在Java中提供了synchronized
關(guān)鍵字來保證只有一個(gè)線程能夠訪問同步代碼塊。既然已經(jīng)提供了synchronized
關(guān)鍵字,那為何在Java的SDK包中,還會(huì)提供Lock接口呢?這是不是重復(fù)造輪子,多此一舉呢?今天,我們就一起來探討下這個(gè)問題。
問題?
既然JVM中提供了synchronized關(guān)鍵字來保證只有一個(gè)線程能夠訪問同步代碼塊,為何還要提供Lock接口呢?這是在重復(fù)造輪子嗎?Java的設(shè)計(jì)者們?yōu)楹我@樣做呢?讓我們一起帶著疑問往下看。
一、為何提供Lock接口?
很多小伙伴可能會(huì)聽說過,在Java 1.5版本中,synchronized的性能不如Lock,但在Java 1.6版本之后,synchronized
做了很多優(yōu)化,性能提升了不少。那既然synchronized關(guān)鍵字的性能已經(jīng)提升了,那為何還要使用Lock呢?
如果我們向更深層次思考的話,就不難想到了:我們使用synchronized
加鎖是無法主動(dòng)釋放鎖的,這就會(huì)涉及到死鎖的問題。
二、死鎖問題
如果要發(fā)生死鎖,則必須存在以下四個(gè)必要條件,四者缺一不可。
- 互斥條件
在一段時(shí)間內(nèi)某資源僅為一個(gè)線程所占有。此時(shí)若有其他線程請(qǐng)求該資源,則請(qǐng)求線程只能等待。
- 不可剝奪條件
線程所獲得的資源在未使用完畢之前,不能被其他線程強(qiáng)行奪走,即只能由獲得該資源的線程自己來釋放(只能是主動(dòng)釋放)。
- 請(qǐng)求與保持條件
線程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求,而該資源已被其他線程占有,此時(shí)請(qǐng)求線程被阻塞,但對(duì)自己已獲得的資源保持不放。
- 循環(huán)等待條件
在發(fā)生死鎖時(shí)必然存在一個(gè)進(jìn)程等待隊(duì)列{P1,P2,…,Pn},其中P1等待P2占有的資源,P2等待P3占有的資源,…,Pn等待P1占有的資源,形成一個(gè)進(jìn)程等待環(huán)路,環(huán)路中每一個(gè)進(jìn)程所占有的資源同時(shí)被另一個(gè)申請(qǐng),也就是前一個(gè)進(jìn)程占有后一個(gè)進(jìn)程所深情地資源。
三、synchronized的局限性
如果我們的程序使用synchronized
關(guān)鍵字發(fā)生了死鎖時(shí),synchronized關(guān)鍵是是無法破壞“不可剝奪”這個(gè)死鎖的條件的。這是因?yàn)閟ynchronized申請(qǐng)資源的時(shí)候, 如果申請(qǐng)不到, 線程直接進(jìn)入阻塞狀態(tài)了, 而線程進(jìn)入阻塞狀態(tài), 啥都干不了, 也釋放不了線程已經(jīng)占有的資源。
然而,在大部分場(chǎng)景下,我們都是希望“不可剝奪”這個(gè)條件能夠被破壞。也就是說對(duì)于“不可剝奪”這個(gè)條件,占用部分資源的線程進(jìn)一步申請(qǐng)其他資源時(shí), 如果申請(qǐng)不到, 可以主動(dòng)釋放它占有的資源, 這樣不可剝奪這個(gè)條件就破壞掉了。
如果我們自己重新設(shè)計(jì)鎖來解決synchronized
的問題,我們?cè)撊绾卧O(shè)計(jì)呢?
四、解決問題
了解了synchronized的局限性之后,如果是讓我們自己實(shí)現(xiàn)一把同步鎖,我們?cè)撊绾卧O(shè)計(jì)呢?也就是說,我們?cè)谠O(shè)計(jì)鎖的時(shí)候,要如何解決synchronized的局限性問題呢?這里,我覺得可以從三個(gè)方面來思考這個(gè)問題。
(1)能夠響應(yīng)中斷。 synchronized
的問題是, 持有鎖A后, 如果嘗試獲取鎖B失敗, 那么線程就進(jìn)入阻塞狀態(tài), 一旦發(fā)生死鎖, 就沒有任何機(jī)會(huì)來喚醒阻塞的線程。 但如果阻塞狀態(tài)的線程能夠響應(yīng)中斷信號(hào), 也就是說當(dāng)我們給阻塞的線程發(fā)送中斷信號(hào)的時(shí)候, 能夠喚醒它, 那它就有機(jī)會(huì)釋放曾經(jīng)持有的鎖A。 這樣就破壞了不可剝奪條件了。
(2)支持超時(shí)。 如果線程在一段時(shí)間之內(nèi)沒有獲取到鎖, 不是進(jìn)入阻塞狀態(tài), 而是返回一個(gè)錯(cuò)誤, 那這個(gè)線程也有機(jī)會(huì)釋放曾經(jīng)持有的鎖。 這樣也能破壞不可剝奪條件。
(3)非阻塞地獲取鎖。 如果嘗試獲取鎖失敗, 并不進(jìn)入阻塞狀態(tài), 而是直接返回, 那這個(gè)線程也有機(jī)會(huì)釋放曾經(jīng)持有的鎖。 這樣也能破壞不可剝奪條件。
體現(xiàn)在Lock接口上,就是Lock接口提供的三個(gè)方法,
如下所示:
// 支持中斷的API void lockInterruptibly() throws InterruptedException; // 支持超時(shí)的API boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 支持非阻塞獲取鎖的API boolean tryLock();
- lockInterruptibly()
支持中斷。
- tryLock()方法
tryLock()方法是有返回值的,它表示用來嘗試獲取鎖,如果獲取成功,則返回true,如果獲取失敗(即鎖已被其他線程獲?。?,則返回false,也就說這個(gè)方法無論如何都會(huì)立即返回。在拿不到鎖時(shí)不會(huì)一直在那等待。
- tryLock(long time, TimeUnit unit)方法
tryLock
(long time, TimeUnit unit)方法和tryLock()方法是類似的,只不過區(qū)別在于這個(gè)方法在拿不到鎖時(shí)會(huì)等待一定的時(shí)間,在時(shí)間期限之內(nèi)如果還拿不到鎖,就返回false。如果一開始拿到鎖或者在等待期間內(nèi)拿到了鎖,則返回true。
也就是說,對(duì)于死鎖問題,Lock能夠破壞不可剝奪的條件,例如,我們下面的程序代碼就破壞了死鎖的不可剝奪的條件。
public class TansferAccount{ private Lock thisLock = new ReentrantLock(); private Lock targetLock = new ReentrantLock(); //賬戶的余額 private Integer balance; //轉(zhuǎn)賬操作 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下面有一個(gè)ReentrantLock
,而ReentrantLock
支持公平鎖和非公平鎖。
在使用ReentrantLock的時(shí)候, ReentrantLock中有兩個(gè)構(gòu)造函數(shù), 一個(gè)是無參構(gòu)造函數(shù), 一個(gè)是傳入fair參數(shù)的構(gòu)造函數(shù)。 fair參數(shù)代表的是鎖的公平策略, 如果傳入true就表示需要構(gòu)造一個(gè)公平鎖, 反之則表示要構(gòu)造一個(gè)非公平鎖。如下代碼片段所示。
//無參構(gòu)造函數(shù): 默認(rèn)非公平鎖 public ReentrantLock() { sync = new NonfairSync(); } //根據(jù)公平策略參數(shù)創(chuàng)建鎖 public ReentrantLock(boolean fair){ sync = fair ? new FairSync() : new NonfairSync(); }
鎖的實(shí)現(xiàn)在本質(zhì)上都對(duì)應(yīng)著一個(gè)入口等待隊(duì)列, 如果一個(gè)線程沒有獲得鎖, 就會(huì)進(jìn)入等待隊(duì)列, 當(dāng)有線程釋放鎖的時(shí)候, 就需要從等待隊(duì)列中喚醒一個(gè)等待的線程。 如果是公平鎖, 喚醒的策略就是誰等待的時(shí)間長, 就喚醒誰, 很公平; 如果是非公平鎖, 則不提供這個(gè)公平保證, 有可能等待時(shí)間短的線程反而先被喚醒。 而Lock是支持公平鎖的,synchronized不支持公平鎖。
最后,值得注意的是,在使用Lock加鎖時(shí),一定要在finally{}
代碼塊中釋放鎖,例如,下面的代碼片段所示。
try{ lock.lock(); }finally{ lock.unlock(); }
注:其他synchronized和Lock的詳細(xì)說明,小伙伴們自行查閱即可。
到此這篇關(guān)于Java中提供synchronized后為什么還要提供Lock的文章就介紹到這了,更多相關(guān)Java中 synchronized Lock內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Java多線程并發(fā)編程 Synchronized關(guān)鍵字
- Java中synchronized用法匯總
- Java并發(fā)系列之JUC中的Lock鎖與synchronized同步代碼塊問題
- Java中線程狀態(tài)+線程安全問題+synchronized的用法詳解
- Java 深入淺出分析Synchronized原理與Callable接口
- Java對(duì)象級(jí)別與類級(jí)別的同步鎖synchronized語法示例
- Java synchronized同步方法詳解
- Java多線程之synchronized同步代碼塊詳解
- Java多線程并發(fā)synchronized?關(guān)鍵字
相關(guān)文章
Java并發(fā)工具類Exchanger的相關(guān)知識(shí)總結(jié)
今天給大家?guī)淼奈恼率荍ava工具類Exchanger的相關(guān)知識(shí)總結(jié),文中有非常詳細(xì)的介紹及代碼示例,對(duì)正在學(xué)習(xí)java的小伙伴們很有幫助,需要的朋友可以參考下2021-06-06詳解SpringBoot與SpringCloud的版本對(duì)應(yīng)詳細(xì)版
這篇文章主要介紹了詳解SpringBoot與SpringCloud的版本對(duì)應(yīng)詳細(xì)版,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09Java程序啟動(dòng)時(shí)初始化數(shù)據(jù)的四種方式
本文主要介紹了Java程序啟動(dòng)時(shí)初始化數(shù)據(jù)的四種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-02-02SpringBoot整合Redis實(shí)現(xiàn)高并發(fā)數(shù)據(jù)緩存的示例講解
這篇文章主要介紹了SpringBoot整合Redis實(shí)現(xiàn)高并發(fā)數(shù)據(jù)緩存,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03druid ParserException類錯(cuò)誤問題及解決
這篇文章主要介紹了druid ParserException類錯(cuò)誤問題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-12-12Java?深入理解創(chuàng)建型設(shè)計(jì)模式之抽象工廠模式
當(dāng)系統(tǒng)所提供的工廠所需生產(chǎn)的具體產(chǎn)品并不是一個(gè)簡單的對(duì)象,而是多個(gè)位于不同產(chǎn)品等級(jí)結(jié)構(gòu)中屬于不同類型的具體產(chǎn)品時(shí)需要使用抽象工廠模式,抽象工廠模式是所有形式的工廠模式中最為抽象和最具一般性的一種形態(tài)2022-02-02springboot動(dòng)態(tài)定時(shí)任務(wù)的實(shí)現(xiàn)方法示例
這篇文章主要給大家介紹了關(guān)于springboot動(dòng)態(tài)定時(shí)任務(wù)的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02