Java并發(fā)編程之ReentrantLock可重入鎖的實(shí)例代碼
目錄 1.ReentrantLock可重入鎖概述2.可重入3.可打斷4.鎖超時(shí)5.公平鎖6.條件變量 Condition
1.ReentrantLock可重入鎖概述
相對于 synchronized 它具備如下特點(diǎn)
可中斷
synchronized鎖加上去不能中斷,a線程應(yīng)用鎖,b線程不能取消掉它
可以設(shè)置超時(shí)時(shí)間
synchronized它去獲取鎖時(shí),如果對方持有鎖,那么它就會進(jìn)入entryList一直等待下去。而可重入鎖可以設(shè)置超時(shí)時(shí)間,規(guī)定時(shí)間內(nèi)如果獲取不到鎖,就放棄鎖
可以設(shè)置為公平鎖
防止線程饑餓的情況,即先到先得。如果爭搶的人比較多,則可能會發(fā)生永遠(yuǎn)都得不到鎖
支持多個(gè)條件變量多個(gè)waitset(不支持條件一的去a不支持條件二的去b)
synchronized只支持同一個(gè)waitset.
與 synchronized 一樣,都支持可重入
基本語法
// 獲取鎖
reentrantLock.lock();
try {
// 臨界區(qū)
} finally {
// 釋放鎖
reentrantLock.unlock();
}
synchronized是在關(guān)鍵字的級別來保護(hù)臨界區(qū),而reentrantLock是在對象的級別保護(hù)臨界區(qū)。臨界區(qū)即訪問共享資源的那段代碼。finally中表明不管將來是否出現(xiàn)異常,都會釋放鎖,釋放鎖即調(diào)用unlock方法。否則無法釋放鎖,其它線程就永遠(yuǎn)也獲取不了鎖。
2.可重入
可重入是指同一個(gè)線程如果首次獲得了這把鎖,那么因?yàn)樗沁@把鎖的擁有者,因此有權(quán)利再次獲取這把鎖
如果是不可重入鎖,那么第二次獲得鎖時(shí),自己也會被鎖擋住
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
3.可打斷
可打斷是指在等待鎖的過程中,其它線程可以用interrupt方法終止我的等待。synchronized鎖是不可打斷的。
我們要想在等鎖的過程中被打斷,就要使用lockInterruptibly()方法對lock對象加鎖,而不是lock()方法
public class TestReentranLock2 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
try {
//如果沒有競爭,此方法就會獲取lock對象的鎖
//如果有競爭,就進(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 也不會讓等待中斷,即不是。即使用lock()方法。
這種方式可以避免死鎖情況的發(fā)生,避免無休止的等待。
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("啟動...");
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í)際上可打斷這種方式屬于一種被動的避免死等,是由其它線程interrupt來打斷。
而鎖超時(shí)是主動的方式避免死等的手段。
獲取鎖用tryLock()方法,即嘗試獲得鎖,如果成功了,它就獲得鎖,如果失敗了,它就可以不去進(jìn)入阻塞隊(duì)列等待,它就會返回false,表示沒有獲得鎖。
立刻失敗
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
System.out.println("啟動...");
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();
}
}
獲得了鎖
啟動...
獲取不到鎖,立刻失敗,返回
超時(shí)失敗
lock.tryLock(1,TimeUnit.SECONDS)表示嘗試等待1s,如果主線程不釋放鎖,那么它就會返回false,如果釋放了鎖,那么它就會返回true.tryLock也支持被打斷,被打斷時(shí)報(bào)異常。
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("啟動...");
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 - 啟動...
18:19:41.547 [t1] c.TestTimeout - 獲取等待 1s 后失敗,返回
5.公平鎖
對于synchronized來說,它是不公平的鎖。當(dāng)一個(gè)線程持有鎖,其他線程就會進(jìn)入阻塞隊(duì)列等待,當(dāng)鎖的持有者釋放鎖的時(shí)候,這些線程就會一擁而上,誰先搶到,誰就成為monitor的主人,而不會按照先來先得的規(guī)則。
ReentrantLock 默認(rèn)是不公平的
ReentrantLock有一個(gè)帶參構(gòu)造方法。默認(rèn)是非公平的。
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
我們可以通過布爾值改成真,來保證它的公平性。即將來阻塞隊(duì)列里的線程,爭搶鎖的時(shí)候會按照進(jìn)入阻塞隊(duì)列的順序執(zhí)行,先到先得。
6.條件變量 Condition
synchronized 中也有條件變量,就是我們講原理時(shí)那個(gè) waitSet 休息室,當(dāng)條件不滿足時(shí)進(jìn)入 waitSet 等待
ReentrantLock 的條件變量比 synchronized 強(qiáng)大之處在于,它是支持多個(gè)條件變量的,這就好比
synchronized 是那些不滿足條件的線程都在一間休息室等消息
而 ReentrantLock 支持多間休息室,有專門等煙的休息室、專門等早餐的休息室、喚醒時(shí)也是按休息室來喚醒
使用要點(diǎn):
- await 前需要獲得鎖
- await 執(zhí)行后,會釋放鎖,進(jìn)入 conditionObject 等待
- await 的線程被喚醒(或打斷、或超時(shí))取重新競爭 lock 鎖
- 競爭 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("送煙來了");
hasCigrette = true;
waitCigaretteQueue.signal();
} finally {
lock.unlock();
}
}
private static void sendBreakfast() {
lock.lock();
try {
log.debug("送早餐來了");
hasBreakfast = true;
waitbreakfastQueue.signal();
} finally {
lock.unlock();
}
}
輸出
18:52:27.680 [main] c.TestCondition - 送早餐來了
18:52:27.682 [Thread-1] c.TestCondition - 等到了它的早餐
18:52:28.683 [main] c.TestCondition - 送煙來了
18:52:28.683 [Thread-0] c.TestCondition - 等到了它的煙
到此這篇關(guān)于Java并發(fā)編程之ReentrantLock可重入鎖的實(shí)例代碼的文章就介紹到這了,更多相關(guān)Java ReentrantLock可重入鎖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
jdk7 中HashMap的知識點(diǎn)總結(jié)
HashMap的原理是老生常談了,不作仔細(xì)解說。一句話概括為HashMap是一個(gè)散列表,它存儲的內(nèi)容是鍵值對(key-value)映射。這篇文章主要總結(jié)了關(guān)于jdk7 中HashMap的知識點(diǎn),需要的朋友可以參考借鑒,一起來看看吧。2017-01-01
詳解SpringMVC實(shí)現(xiàn)圖片上傳以及該注意的小細(xì)節(jié)
本篇文章主要介紹了詳解SpringMVC實(shí)現(xiàn)圖片上傳以及該注意的小細(xì)節(jié),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-02-02
SpringCloud啟動eureka server后,沒報(bào)錯卻不能訪問管理頁面(404問題)
這篇文章主要介紹了SpringCloud啟動eureka server后,沒報(bào)錯卻不能訪問管理頁面(404問題),具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2023-11-11
Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?
這篇文章主要介紹了Java中遍歷數(shù)組使用foreach循環(huán)還是for循環(huán)?本文著重講解for語句的語法并給出使用實(shí)例,同時(shí)總結(jié)出盡量使用foreach語句遍歷數(shù)組,需要的朋友可以參考下2015-06-06
SpringBoot中@PostConstruct 注解的實(shí)現(xiàn)
在Spring Boot框架中,?@PostConstruct是一個(gè)非常有用的注解,它用于在依賴注入完成后執(zhí)行初始化方法,本文將介紹?@PostConstruct的基本概念、使用場景以及提供詳細(xì)的代碼示例,感興趣的可以了解一下2024-09-09
Spring Cloud Ribbon客戶端詳細(xì)介紹
Spring Cloud Ribbon 是一套基于 Netflix Ribbon 實(shí)現(xiàn)的客戶端負(fù)載均衡和服務(wù)調(diào)用工具。通過Spring Cloud的封裝,可以讓我們輕松地將面向服務(wù)的REST模版請求自動轉(zhuǎn)換成客戶端負(fù)載均衡的服務(wù)調(diào)用2022-09-09

