Java中的可重入鎖ReentrantLock簡析
可重入鎖ReentrantLock
相對于 synchronized 它具備如下特點
- 可中斷
- 可以設(shè)置超時時間
- 可以設(shè)置為公平鎖
- 支持多個條件變量
與 synchronized 一樣,都支持可重入
//基本語法 // 獲取鎖 reentrantLock.lock(); try{ // 臨界區(qū) } finally{ // 釋放鎖 reentrantLock.unlock(); }
1、可重入
可重入是指同一個線程如果首次獲得了這把鎖,那么因為它是這把鎖的擁有者,因此有權(quán)利再次獲取這把鎖 如果是不可重入鎖,那么第二次獲得鎖時,自己也會被鎖擋住
@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被主線程打斷后,并沒異常拋出錯誤,而是正常執(zhí)行
3、鎖超時
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線程嘗試去獲取鎖的時候就會立即失敗
2)超時失敗
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線程在嘗試獲取鎖時,阻塞了一秒鐘,一秒后還是沒有獲取到鎖,則返回false
4、公平鎖
公平鎖是指多個線程按照申請鎖的順序來獲取鎖,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個線程是先啟動的,應(yīng)該先獲取到鎖,但是實際情況,強(qiáng)制插入線程中途就獲取到鎖了
5、條件變量
synchronized 中也有條件變量,就是我們講原理時那個 waitSet 休息室,當(dāng)條件不滿足時進(jìn)入 waitSet 等待 ReentrantLock 的條件變量比 synchronized 強(qiáng)大之處在于,它是支持多個條件變量的,這就好比
- synchronized 是那些不滿足條件的線程都在一間休息室等消息
- 而 ReentrantLock 支持多間休息室,有專門等煙的休息室、專門等早餐的休息室、喚醒時也是按休息室來喚醒
使用要點:
- await 前需要獲得鎖
- await 執(zhí)行后,會釋放鎖,進(jìn)入 conditionObject 等待
- await 的線程被喚醒(或打斷、或超時)取重新競爭 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語句實現(xiàn)IN查詢(三種)
這篇文章主要介紹了mybatis使用foreach語句實現(xiàn)IN查詢(三種),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12MyBatis動態(tài)sql查詢及多參數(shù)查詢方式
這篇文章主要介紹了MyBatis動態(tài)sql查詢及多參數(shù)查詢方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-10-10springcloud本地調(diào)試feign調(diào)用出現(xiàn)的詭異404問題及解決
這篇文章主要介紹了springcloud本地調(diào)試feign調(diào)用出現(xiàn)的詭異404問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03Java將CSV的數(shù)據(jù)發(fā)送到kafka的示例
這篇文章主要介紹了Java將CSV的數(shù)據(jù)發(fā)送到kafka得示例,幫助大家更好得理解和使用Java,感興趣的朋友可以了解下2020-11-11SpringBoot中@ComponentScan注解過濾排除不加載某個類的3種方法
這篇文章主要給大家介紹了關(guān)于SpringBoot中@ComponentScan注解過濾排除不加載某個類的3種方法,文中通過實例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用SpringBoot具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2023-07-07EasyExcel實現(xiàn)讀取excel中的日期單元格并自動判定終止讀取
這篇文章主要為大家詳細(xì)介紹了EasyExcel如何實現(xiàn)讀取excel中的日期單元格并自動判定終止讀取,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-11-11