目錄 1.ReentrantLock可重入鎖概述2.可重入3.可打斷4.鎖超時5.公平鎖6.條件變量 Condition
1.ReentrantLock可重入鎖概述
相對于 synchronized 它具備如下特點
可中斷
synchronized鎖加上去不能中斷,a線程應用鎖,b線程不能取消掉它
可以設置超時時間
synchronized它去獲取鎖時,如果對方持有鎖,那么它就會進入entryList一直等待下去。而可重入鎖可以設置超時時間,規定時間內如果獲取不到鎖,就放棄鎖
可以設置為公平鎖
防止線程饑餓的情況,即先到先得。如果爭搶的人比較多,則可能會發生永遠都得不到鎖
支持多個條件變量多個waitset(不支持條件一的去a不支持條件二的去b)
synchronized只支持同一個waitset.
與 synchronized 一樣,都支持可重入
基本語法
1
2
3
4
5
6
7
8
|
// 獲取鎖 reentrantLock.lock(); try { // 臨界區 } finally { // 釋放鎖 reentrantLock.unlock(); } |
synchronized是在關鍵字的級別來保護臨界區,而reentrantLock是在對象的級別保護臨界區。臨界區即訪問共享資源的那段代碼。finally中表明不管將來是否出現異常,都會釋放鎖,釋放鎖即調用unlock方法。否則無法釋放鎖,其它線程就永遠也獲取不了鎖。
2.可重入
可重入是指同一個線程如果首次獲得了這把鎖,那么因為它是這把鎖的擁有者,因此有權利再次獲取這把鎖
如果是不可重入鎖,那么第二次獲得鎖時,自己也會被鎖擋住
ReentrantLock和synchronized都是可重入鎖。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
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(); } } } |
1
2
3
|
execute method1 execute method2 execute method3 |
3.可打斷
可打斷是指在等待鎖的過程中,其它線程可以用interrupt方法終止我的等待。synchronized鎖是不可打斷的。
我們要想在等鎖的過程中被打斷,就要使用lockInterruptibly()方法對lock對象加鎖,而不是lock()方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
public class TestReentranLock2 { public static void main(String[] args) { ReentrantLock lock = new ReentrantLock(); Thread t1 = new Thread(() -> { try { //如果沒有競爭,此方法就會獲取lock對象的鎖 //如果有競爭,就進入阻塞隊列等待,可以被其它線程用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( "執行打斷t1" ); } finally { lock.unlock(); } } } |
1
2
3
4
5
6
7
8
9
10
|
主線程獲得了鎖 嘗試獲得鎖 執行打斷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()方法。
這種方式可以避免死鎖情況的發生,避免無休止的等待。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
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( "執行打斷" ); sleep( 1 ); } finally { System.out.println( "釋放了鎖" ); lock.unlock(); } |
4.鎖超時
ReentranLock支持可打斷,其實就是為了避免死等,這樣就可以減少死鎖的發生。實際上可打斷這種方式屬于一種被動的避免死等,是由其它線程interrupt來打斷。
而鎖超時是主動的方式避免死等的手段。
獲取鎖用tryLock()方法,即嘗試獲得鎖,如果成功了,它就獲得鎖,如果失敗了,它就可以不去進入阻塞隊列等待,它就會返回false,表示沒有獲得鎖。
立刻失敗
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
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(); } } |
獲得了鎖
啟動...
獲取不到鎖,立刻失敗,返回
超時失敗
lock.tryLock(1,TimeUnit.SECONDS)表示嘗試等待1s,如果主線程不釋放鎖,那么它就會返回false,如果釋放了鎖,那么它就會返回true.tryLock也支持被打斷,被打斷時報異常。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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來說,它是不公平的鎖。當一個線程持有鎖,其他線程就會進入阻塞隊列等待,當鎖的持有者釋放鎖的時候,這些線程就會一擁而上,誰先搶到,誰就成為monitor的主人,而不會按照先來先得的規則。
ReentrantLock 默認是不公平的
ReentrantLock有一個帶參構造方法。默認是非公平的。
1
2
3
|
public ReentrantLock( boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } |
我們可以通過布爾值改成真,來保證它的公平性。即將來阻塞隊列里的線程,爭搶鎖的時候會按照進入阻塞隊列的順序執行,先到先得。
6.條件變量 Condition
synchronized 中也有條件變量,就是我們講原理時那個 waitSet 休息室,當條件不滿足時進入 waitSet 等待
ReentrantLock 的條件變量比 synchronized 強大之處在于,它是支持多個條件變量的,這就好比
synchronized 是那些不滿足條件的線程都在一間休息室等消息
而 ReentrantLock 支持多間休息室,有專門等煙的休息室、專門等早餐的休息室、喚醒時也是按休息室來喚醒
使用要點:
- await 前需要獲得鎖
- await 執行后,會釋放鎖,進入 conditionObject 等待
- await 的線程被喚醒(或打斷、或超時)取重新競爭 lock 鎖
- 競爭 lock 鎖成功后,從 await 后繼續執行
- signal 相當于 notify,signalAll 相當于 notifyAll
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
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 - 等到了它的煙
到此這篇關于Java并發編程之ReentrantLock可重入鎖的實例代碼的文章就介紹到這了,更多相關Java ReentrantLock可重入鎖內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/qq_39736597/article/details/113701074