java?ReentrantLock并發(fā)鎖使用詳解
一、ReentrantLock是什么
ReentrantLock是一種基于AQS框架的應(yīng)用實(shí)現(xiàn),是JDK中的一種線程并發(fā)訪問(wèn)的同步手段,它的功能類似于synchronized是一種互斥鎖,可以保證線程安全。
相對(duì)于 synchronized, ReentrantLock具備如下特點(diǎn):
- 可中斷
- 可以設(shè)置超時(shí)時(shí)間
- 可以設(shè)置為公平鎖
- 支持多個(gè)條件變量
- 與 synchronized 一樣,都支持可重入
進(jìn)入源碼可以看到,其實(shí)現(xiàn)了公平鎖和非公平鎖

內(nèi)部實(shí)現(xiàn)了加鎖的操作,并且支持重入鎖。不用我們?cè)僦貙?xiě)

解鎖操作

1-1、ReentrantLock和synchronized區(qū)別
synchronized和ReentrantLock的區(qū)別:
- synchronized是JVM層次的鎖實(shí)現(xiàn),ReentrantLock是JDK層次的鎖實(shí)現(xiàn);
- synchronized的鎖狀態(tài)是無(wú)法在代碼中直接判斷的,但是ReentrantLock可以通過(guò)ReentrantLock#isLocked判斷;
- synchronized是非公平鎖,ReentrantLock是可以是公平也可以是非公平的;
- synchronized是不可以被中斷的,而ReentrantLock#lockInterruptibly方法是可以被中斷的;
- 在發(fā)生異常時(shí)synchronized會(huì)自動(dòng)釋放鎖,而ReentrantLock需要開(kāi)發(fā)者在finally塊中顯示釋放鎖;
- ReentrantLock獲取鎖的形式有多種:如立即返回是否成功的tryLock(),以及等待指定時(shí)長(zhǎng)的獲取,更加靈活;
- synchronized在特定的情況下對(duì)于已經(jīng)在等待的線程是后來(lái)的線程先獲得鎖(回顧一下sychronized的喚醒策略),而ReentrantLock對(duì)于已經(jīng)在等待的線程是先來(lái)的線程先獲得鎖;
1-2、ReentrantLock的使用
1-2-1、ReentrantLock同步執(zhí)行,類似synchronized
使用ReentrantLock需要注意的是:一定要在finally中進(jìn)行解鎖,方式業(yè)務(wù)拋出異常,無(wú)法解鎖
public class ReentrantLockDemo {
private static int sum = 0;
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(()->{
//加鎖
lock.lock();
try {
// 臨界區(qū)代碼
// TODO 業(yè)務(wù)邏輯:讀寫(xiě)操作不能保證線程安全
for (int j = 0; j < 10000; j++) {
sum++;
}
} finally {
// 解鎖--一定要在finally中解鎖,防止業(yè)務(wù)代碼異常,無(wú)法釋放鎖
lock.unlock();
}
});
thread.start();
}
Thread.sleep(2000);
System.out.println(sum);
}
}
測(cè)試結(jié)果:

1-2-2、可重入鎖
可重入鎖就是 A(加鎖)-->調(diào)用--->B(加鎖)-->調(diào)用-->C(加鎖),從A到C即使B/C都有加鎖,也可以進(jìn)入
@Slf4j
public class ReentrantLockDemo2 {
public 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();
}
}
}
執(zhí)行結(jié)果:

1-2-3、鎖中斷
可以使用lockInterruptibly來(lái)進(jìn)行鎖中斷
lockInterruptibly()方法能夠中斷等待獲取鎖的線程。當(dāng)兩個(gè)線程同時(shí)通過(guò)lock.lockInterruptibly()獲取某個(gè)鎖時(shí),假若此時(shí)線程A獲取到了鎖,而線程B只有等待,那么對(duì)線程B調(diào)用threadB.interrupt()方法能夠中斷線程B的等待過(guò)程。
public class ReentrantLockDemo3 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1啟動(dòng)...");
try {
lock.lockInterruptibly();
try {
log.debug("t1獲得了鎖");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("t1等鎖的過(guò)程中被中斷");
}
}, "t1");
lock.lock();
try {
log.debug("main線程獲得了鎖");
t1.start();
//先讓線程t1執(zhí)行
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t1.interrupt();
log.debug("線程t1執(zhí)行中斷");
} finally {
lock.unlock();
}
}
}
執(zhí)行結(jié)果:

1-2-4、獲得鎖超時(shí)失敗
可以讓線程等待指定的時(shí)間,如果還未獲取鎖則進(jìn)行失敗處理。
如下代碼,首先讓主線程獲得鎖,然后讓子線程啟動(dòng)嘗試獲取鎖,但是由于主線程獲取鎖之后,讓線程等待了2秒,而子線程獲得鎖的超時(shí)時(shí)間只有1秒,如果未獲得鎖,則進(jìn)行return失敗處理
public class ReentrantLockDemo4 {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Thread t1 = new Thread(() -> {
log.debug("t1啟動(dòng)...");
//超時(shí)
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)) {
log.debug("等待 1s 后獲取鎖失敗,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
try {
log.debug("t1獲得了鎖");
} finally {
lock.unlock();
}
}, "t1");
lock.lock();
try {
log.debug("main線程獲得了鎖");
t1.start();
//先讓線程t1執(zhí)行
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
執(zhí)行結(jié)果:

1-2-5、公平鎖
ReentrantLock 默認(rèn)是不公平的

首先啟動(dòng)500次for循環(huán)創(chuàng)建500個(gè)線程,然后進(jìn)行加鎖操作,并同時(shí)啟動(dòng)了。這樣這500個(gè)線程就依次排隊(duì)等待加鎖的處理
下面500個(gè)線程也是等待加鎖操作
如果使用公平鎖,下面500的線程只有等上面500個(gè)線程運(yùn)行完成之后才能獲得鎖。
@Slf4j
public class ReentrantLockDemo5 {
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock(true); //公平鎖
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "t" + i).start();
}
// 1s 之后去爭(zhēng)搶鎖
Thread.sleep(1000);
for (int i = 0; i < 500; i++) {
new Thread(() -> {
lock.lock();
try {
log.debug(Thread.currentThread().getName() + " running...");
} finally {
lock.unlock();
}
}, "強(qiáng)行插入" + i).start();
}
}
}
測(cè)試結(jié)果(后進(jìn)入的線程都在等待排隊(duì))

使用非公平鎖的情況下,就可以看到下面500線程有些線程就可以搶占鎖了

那ReentrantLock為什么默認(rèn)使用非公平鎖呢?實(shí)際上就是為了提高性能,如果使用公平鎖,當(dāng)前鎖對(duì)象釋放之后,還需要去隊(duì)列中獲取第一個(gè)排隊(duì)的線程,然后進(jìn)行加鎖處理。而非公平鎖,可能再當(dāng)前對(duì)象釋放鎖之后,正好有新的線程在獲取鎖,這樣就可以直接進(jìn)行加鎖操作,不必再去隊(duì)列中讀取。
以上就是java ReentrantLock并發(fā)鎖使用詳解的詳細(xì)內(nèi)容,更多關(guān)于java ReentrantLock并發(fā)鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot 多數(shù)據(jù)源配置不生效遇到的坑及解決
這篇文章主要介紹了springboot 多數(shù)據(jù)源配置不生效遇到的坑及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11
Intellij idea下使用不同tomcat編譯maven項(xiàng)目的服務(wù)器路徑方法詳解
今天小編就為大家分享一篇關(guān)于Intellij idea下使用不同tomcat編譯maven項(xiàng)目的服務(wù)器路徑方法詳解,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-02-02
Java 數(shù)據(jù)結(jié)構(gòu)與算法系列精講之KMP算法
在很多地方也都經(jīng)??吹街v解KMP算法的文章,看久了好像也知道是怎么一回事,但總感覺(jué)有些地方自己還是沒(méi)有完全懂明白。這兩天花了點(diǎn)時(shí)間總結(jié)一下,有點(diǎn)小體會(huì),我希望可以通過(guò)我自己的語(yǔ)言來(lái)把這個(gè)算法的一些細(xì)節(jié)梳理清楚,也算是考驗(yàn)一下自己有真正理解這個(gè)算法2022-02-02
Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹
這篇文章主要介紹了Java設(shè)計(jì)模式之工廠模式(Factory模式)介紹,本文講解了為何使用工廠模式、工廠方法、抽象工廠、Java工廠模式舉例等內(nèi)容,需要的朋友可以參考下2015-03-03
基于SpringBoot使用MyBatis插件的問(wèn)題
MyBatis-Plus并不能為我們解決所有問(wèn)題,例如一些復(fù)雜的SQL,多表聯(lián)查,我們就需要自己去編寫(xiě)代碼和SQL語(yǔ)句,我們?cè)撊绾慰焖俚慕鉀Q這個(gè)問(wèn)題呢,這個(gè)時(shí)候可以使用MyBatisX插件,今天小編給大家?guī)?lái)了SpringBoot使用MyBatis插件問(wèn)題,感興趣的朋友一起看看吧2022-03-03
SpringCloud筆記(Hoxton)Netflix之Ribbon負(fù)載均衡示例代碼
這篇文章主要介紹了SpringCloud筆記HoxtonNetflix之Ribbon負(fù)載均衡,Ribbon是管理HTTP和TCP服務(wù)客戶端的負(fù)載均衡器,Ribbon具有一系列帶有名稱的客戶端(Named?Client),對(duì)SpringCloud?Ribbon負(fù)載均衡相關(guān)知識(shí)感興趣的朋友一起看看吧2022-06-06
使用IDEA創(chuàng)建Servlet程序的詳細(xì)步驟
在學(xué)習(xí)servlet過(guò)程中,參考的教程是用eclipse完成的,而我在練習(xí)的過(guò)程中是使用IDEA的,在創(chuàng)建servlet程序時(shí)遇到了挺多困難,在此記錄一下如何用IDEA完整創(chuàng)建一個(gè)servlet程序,感興趣的朋友一起看看吧2024-08-08
java多線程編程之向線程傳遞數(shù)據(jù)的三種方法
在多線程的異步開(kāi)發(fā)模式下,數(shù)據(jù)的傳遞和返回和同步開(kāi)發(fā)模式有很大的區(qū)別。由于線程的運(yùn)行和結(jié)束是不可預(yù)料的,因此,在傳遞和返回?cái)?shù)據(jù)時(shí)就無(wú)法象函數(shù)一樣通過(guò)函數(shù)參數(shù)和return語(yǔ)句來(lái)返回?cái)?shù)據(jù)2014-01-01
java求最大公約數(shù)與最小公倍數(shù)的方法示例
這篇文章主要介紹了java求最大公約數(shù)與最小公倍數(shù)的方法,涉及java數(shù)值運(yùn)算的相關(guān)操作技巧,并附帶分析了eclipse環(huán)境下設(shè)置運(yùn)行輸入?yún)?shù)的相關(guān)操作技巧,需要的朋友可以參考下2017-11-11

