Java中的可重入鎖ReentrantLock簡析
可重入鎖ReentrantLock
相對于 synchronized 它具備如下特點(diǎn)
- 可中斷
- 可以設(shè)置超時(shí)時(shí)間
- 可以設(shè)置為公平鎖
- 支持多個(gè)條件變量
與 synchronized 一樣,都支持可重入
//基本語法
// 獲取鎖
reentrantLock.lock();
try{
// 臨界區(qū)
} finally{
// 釋放鎖
reentrantLock.unlock();
}1、可重入
可重入是指同一個(gè)線程如果首次獲得了這把鎖,那么因?yàn)樗沁@把鎖的擁有者,因此有權(quán)利再次獲取這把鎖 如果是不可重入鎖,那么第二次獲得鎖時(shí),自己也會被鎖擋住
@Slf4j(topic = "c.TestReentrant")
public class TestReentrant {
static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
method1();
}
public static void method1() {
lock.lock();
try {
log.debug("execute method1");
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
lock.lock();
try {
log.debug("execute method2");
method3();
} finally {
lock.unlock();
}
}
public static void method3() {
lock.lock();
try {
log.debug("execute method3");
} finally {
lock.unlock();
}
}
}由結(jié)果可以證明:ReentrantLock 是可重入的

2、可打斷
舉例說明:
@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {
public static void main(String[] args) {
test1();
}
private static void test1() {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("啟動...");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("等鎖的過程中被打斷");
return;
}
try {
log.debug("獲得了鎖");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("獲得了鎖");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("執(zhí)行打斷");
} finally {
lock.unlock();
}
}
}由結(jié)果可以看到,main線程獲取到了鎖,t1線程嘗試去獲取鎖,然后main線程打斷t1線程嘗試獲取鎖的過程

注意如果是不可中斷模式,那么即使使用了 interrupt 也不會讓等待中斷
@Slf4j(topic = "c.TestInterrupt")
public class TestInterrupt {
public static void main(String[] args) {
test2();
}
private static void test2() {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("啟動...");
lock.lock();
try {
log.debug("獲得了鎖");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("獲得了鎖");
t1.start();
try {
sleep(1);
t1.interrupt();
log.debug("執(zhí)行打斷");
sleep(1);
} finally {
log.debug("釋放了鎖");
lock.unlock();
}
}
}可以看到t1被主線程打斷后,并沒異常拋出錯(cuò)誤,而是正常執(zhí)行

3、鎖超時(shí)
1) 立即失敗的場景
public class TestTimeout {
public static void main(String[] args) {
test2();
}
private static void test2() {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("啟動...");
if (!lock.tryLock()) {
log.debug("獲取立刻失敗,返回");
return;
}
try {
log.debug("獲得了鎖");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
log.debug("獲得了鎖");
t1.start();
try {
sleep(2);
} finally {
lock.unlock();
}
}
}main線程首先獲取到了鎖,t1線程嘗試去獲取鎖的時(shí)候就會立即失敗

2)超時(shí)失敗
public class TestTimeout {
public static void main(String[] args) {
test1();
}
private static void test1() {
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();
}
}
}由結(jié)果可以判斷t1線程在嘗試獲取鎖時(shí),阻塞了一秒鐘,一秒后還是沒有獲取到鎖,則返回false

4、公平鎖
公平鎖是指多個(gè)線程按照申請鎖的順序來獲取鎖,ReentrantLock 默認(rèn)是不公平的
demo演示證明:ReentrantLock 默認(rèn)是不公平的
public class TestFair {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(false);
lock.lock();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去爭搶鎖
Thread.sleep(1000);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "強(qiáng)行插入").start();
}
lock.unlock();
}
}按道理說,500個(gè)線程是先啟動的,應(yīng)該先獲取到鎖,但是實(shí)際情況,強(qiáng)制插入線程中途就獲取到鎖了

5、條件變量
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í)行
@Slf4j(topic = "c.TestCondition")
public class TestCondition {
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();
log.debug("準(zhǔn)備工作,看看有沒有煙: {}", hasCigrette);
while (!hasCigrette) {
log.debug("準(zhǔn)備工作,看看有沒有煙: {}", hasCigrette);
try {
// 釋放鎖,并在該條件變量上等待
waitCigaretteQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("等到了它的煙");
} finally {
lock.unlock();
}
}).start();
new Thread(() -> {
try {
lock.lock();
log.debug("準(zhǔn)備工作,看看有沒有早餐: {}", hasBreakfast);
while (!hasBreakfast) {
log.debug("準(zhǔn)備工作,看看有沒有早餐: {}", 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();
}
}
}
到此這篇關(guān)于Java中的可重入鎖ReentrantLock簡析的文章就介紹到這了,更多相關(guān)可重入鎖ReentrantLock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis使用foreach語句實(shí)現(xiàn)IN查詢(三種)
這篇文章主要介紹了mybatis使用foreach語句實(shí)現(xiàn)IN查詢(三種),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
MyBatis動態(tài)sql查詢及多參數(shù)查詢方式
這篇文章主要介紹了MyBatis動態(tài)sql查詢及多參數(shù)查詢方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
springcloud本地調(diào)試feign調(diào)用出現(xiàn)的詭異404問題及解決
這篇文章主要介紹了springcloud本地調(diào)試feign調(diào)用出現(xiàn)的詭異404問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java將CSV的數(shù)據(jù)發(fā)送到kafka的示例
這篇文章主要介紹了Java將CSV的數(shù)據(jù)發(fā)送到kafka得示例,幫助大家更好得理解和使用Java,感興趣的朋友可以了解下2020-11-11
SpringBoot中@ComponentScan注解過濾排除不加載某個(gè)類的3種方法
這篇文章主要給大家介紹了關(guān)于SpringBoot中@ComponentScan注解過濾排除不加載某個(gè)類的3種方法,文中通過實(shí)例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2023-07-07
EasyExcel實(shí)現(xiàn)讀取excel中的日期單元格并自動判定終止讀取
這篇文章主要為大家詳細(xì)介紹了EasyExcel如何實(shí)現(xiàn)讀取excel中的日期單元格并自動判定終止讀取,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11

