您好,登錄后才能下訂單哦!
本篇文章給大家分享的是有關(guān)如何在Java項(xiàng)目中實(shí)現(xiàn)一個(gè)ReentrantLock可重入鎖,小編覺得挺實(shí)用的,因此分享給大家學(xué)習(xí),希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來(lái)看看吧。
1.ReentrantLock可重入鎖概述
相對(duì)于 synchronized 它具備如下特點(diǎn)
可中斷
synchronized鎖加上去不能中斷,a線程應(yīng)用鎖,b線程不能取消掉它
可以設(shè)置超時(shí)時(shí)間
synchronized它去獲取鎖時(shí),如果對(duì)方持有鎖,那么它就會(huì)進(jìn)入entryList一直等待下去。而可重入鎖可以設(shè)置超時(shí)時(shí)間,規(guī)定時(shí)間內(nèi)如果獲取不到鎖,就放棄鎖
可以設(shè)置為公平鎖
防止線程饑餓的情況,即先到先得。如果爭(zhēng)搶的人比較多,則可能會(huì)發(fā)生永遠(yuǎn)都得不到鎖
支持多個(gè)條件變量多個(gè)waitset(不支持條件一的去a不支持條件二的去b)
synchronized只支持同一個(gè)waitset.
與 synchronized 一樣,都支持可重入
基本語(yǔ)法
// 獲取鎖 reentrantLock.lock(); try { // 臨界區(qū) } finally { // 釋放鎖 reentrantLock.unlock(); }
synchronized是在關(guān)鍵字的級(jí)別來(lái)保護(hù)臨界區(qū),而reentrantLock是在對(duì)象的級(jí)別保護(hù)臨界區(qū)。臨界區(qū)即訪問共享資源的那段代碼。finally中表明不管將來(lái)是否出現(xiàn)異常,都會(huì)釋放鎖,釋放鎖即調(diào)用unlock方法。否則無(wú)法釋放鎖,其它線程就永遠(yuǎn)也獲取不了鎖。
可重入是指同一個(gè)線程如果首次獲得了這把鎖,那么因?yàn)樗沁@把鎖的擁有者,因此有權(quán)利再次獲取這把鎖
如果是不可重入鎖,那么第二次獲得鎖時(shí),自己也會(huì)被鎖擋住
ReentrantLock和synchronized都是可重入鎖。
public class TestReentranLock1 { static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { method1(); } public static void method1() { lock.lock(); try { System.out.println("execute method1"); method2(); } finally { lock.unlock(); } } public static void method2() { lock.lock(); try { System.out.println("execute method2"); method3(); } finally { lock.unlock(); } } public static void method3() { lock.lock(); try { System.out.println("execute method3"); } finally { lock.unlock(); } } }
execute method1 execute method2 execute method3
可打斷是指在等待鎖的過程中,其它線程可以用interrupt方法終止我的等待。synchronized鎖是不可打斷的。
我們要想在等鎖的過程中被打斷,就要使用lockInterruptibly()方法對(duì)lock對(duì)象加鎖,而不是lock()方法
public class TestReentranLock2 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { //如果沒有競(jìng)爭(zhēng),此方法就會(huì)獲取lock對(duì)象的鎖 //如果有競(jìng)爭(zhēng),就進(jìn)入阻塞隊(duì)列等待,可以被其它線程用interrupt打斷 System.out.println("嘗試獲得鎖"); lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); System.out.println("等鎖的過程中被打斷"); return; } try { System.out.println("t1獲得了鎖"); } finally { lock.unlock(); } }, "t1"); lock.lock(); System.out.println("主線程獲得了鎖"); t1.start(); try { try { sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } t1.interrupt(); System.out.println("執(zhí)行打斷t1"); } finally { lock.unlock(); } } }
主線程獲得了鎖 嘗試獲得鎖 執(zhí)行打斷t1 等鎖的過程中被打斷 java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335) at cn.yj.jvm.TestReentranLock2.lambda$main$0(TestReentranLock2.java:15) at java.lang.Thread.run(Thread.java:748)
注意如果是不可中斷模式,那么即使使用了 interrupt 也不會(huì)讓等待中斷,即不是。即使用lock()方法。
這種方式可以避免死鎖情況的發(fā)生,避免無(wú)休止的等待。
ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { System.out.println("啟動(dòng)..."); lock.lock(); try { System.out.println("獲得了鎖"); } finally { lock.unlock(); } }, "t1"); lock.lock(); System.out.println("獲得了鎖"); t1.start(); try { sleep(1); t1.interrupt(); System.out.println("執(zhí)行打斷"); sleep(1); } finally { System.out.println("釋放了鎖"); lock.unlock(); }
4.鎖超時(shí)
ReentranLock支持可打斷,其實(shí)就是為了避免死等,這樣就可以減少死鎖的發(fā)生。實(shí)際上可打斷這種方式屬于一種被動(dòng)的避免死等,是由其它線程interrupt來(lái)打斷。
而鎖超時(shí)是主動(dòng)的方式避免死等的手段。
獲取鎖用tryLock()方法,即嘗試獲得鎖,如果成功了,它就獲得鎖,如果失敗了,它就可以不去進(jìn)入阻塞隊(duì)列等待,它就會(huì)返回false,表示沒有獲得鎖。
立刻失敗
public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { System.out.println("啟動(dòng)..."); if (!lock.tryLock()) { System.out.println("獲取不到鎖,立刻失敗,返回"); return; } try { System.out.println("獲得了鎖"); } finally { lock.unlock(); } }, "t1"); lock.lock(); System.out.println("獲得了鎖"); t1.start(); try { try { sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } } finally { lock.unlock(); } }
獲得了鎖
啟動(dòng)...
獲取不到鎖,立刻失敗,返回
超時(shí)失敗
lock.tryLock(1,TimeUnit.SECONDS)表示嘗試等待1s,如果主線程不釋放鎖,那么它就會(huì)返回false,如果釋放了鎖,那么它就會(huì)返回true.tryLock也支持被打斷,被打斷時(shí)報(bào)異常。
ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { log.debug("啟動(dòng)..."); try { if (!lock.tryLock(1, TimeUnit.SECONDS)) { log.debug("獲取等待 1s 后失敗,返回"); return; } } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("獲得了鎖"); } finally { lock.unlock(); } }, "t1"); lock.lock(); log.debug("獲得了鎖"); t1.start(); try { sleep(2); } finally { lock.unlock(); }
輸出
18:19:40.537 [main] c.TestTimeout - 獲得了鎖
18:19:40.544 [t1] c.TestTimeout - 啟動(dòng)...
18:19:41.547 [t1] c.TestTimeout - 獲取等待 1s 后失敗,返回
對(duì)于synchronized來(lái)說,它是不公平的鎖。當(dāng)一個(gè)線程持有鎖,其他線程就會(huì)進(jìn)入阻塞隊(duì)列等待,當(dāng)鎖的持有者釋放鎖的時(shí)候,這些線程就會(huì)一擁而上,誰(shuí)先搶到,誰(shuí)就成為monitor的主人,而不會(huì)按照先來(lái)先得的規(guī)則。
ReentrantLock 默認(rèn)是不公平的
ReentrantLock有一個(gè)帶參構(gòu)造方法。默認(rèn)是非公平的。
public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
我們可以通過布爾值改成真,來(lái)保證它的公平性。即將來(lái)阻塞隊(duì)列里的線程,爭(zhēng)搶鎖的時(shí)候會(huì)按照進(jìn)入阻塞隊(duì)列的順序執(zhí)行,先到先得。
synchronized 中也有條件變量,就是我們講原理時(shí)那個(gè) waitSet 休息室,當(dāng)條件不滿足時(shí)進(jìn)入 waitSet 等待
ReentrantLock 的條件變量比 synchronized 強(qiáng)大之處在于,它是支持多個(gè)條件變量的,這就好比
synchronized 是那些不滿足條件的線程都在一間休息室等消息
而 ReentrantLock 支持多間休息室,有專門等煙的休息室、專門等早餐的休息室、喚醒時(shí)也是按休息室來(lái)喚醒
使用要點(diǎn):
await 前需要獲得鎖
await 執(zhí)行后,會(huì)釋放鎖,進(jìn)入 conditionObject 等待
await 的線程被喚醒(或打斷、或超時(shí))取重新競(jìng)爭(zhēng) lock 鎖
競(jìng)爭(zhēng) lock 鎖成功后,從 await 后繼續(xù)執(zhí)行
signal 相當(dāng)于 notify,signalAll 相當(dāng)于 notifyAll
static ReentrantLock lock = new ReentrantLock(); static Condition waitCigaretteQueue = lock.newCondition(); static Condition waitbreakfastQueue = lock.newCondition(); static volatile boolean hasCigrette = false; static volatile boolean hasBreakfast = false; public static void main(String[] args) { new Thread(() -> { try { lock.lock(); while (!hasCigrette) { try { waitCigaretteQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("等到了它的煙"); } finally { lock.unlock(); } }).start(); new Thread(() -> { try { lock.lock(); while (!hasBreakfast) { try { waitbreakfastQueue.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("等到了它的早餐"); } finally { lock.unlock(); } }).start(); sleep(1); sendBreakfast(); sleep(1); sendCigarette(); } private static void sendCigarette() { lock.lock(); try { log.debug("送煙來(lái)了"); hasCigrette = true; waitCigaretteQueue.signal(); } finally { lock.unlock(); } } private static void sendBreakfast() { lock.lock(); try { log.debug("送早餐來(lái)了"); hasBreakfast = true; waitbreakfastQueue.signal(); } finally { lock.unlock(); } }
輸出
18:52:27.680 [main] c.TestCondition - 送早餐來(lái)了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送煙來(lái)了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的煙
以上就是如何在Java項(xiàng)目中實(shí)現(xiàn)一個(gè)ReentrantLock可重入鎖,小編相信有部分知識(shí)點(diǎn)可能是我們?nèi)粘9ぷ鲿?huì)見到或用到的。希望你能通過這篇文章學(xué)到更多知識(shí)。更多詳情敬請(qǐng)關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。