重入锁:ReentrantLock 详解
在JDK5.0版本之前,重入鎖的性能遠遠好于synchronized關鍵字,JDK6.0版本之后synchronized 得到了大量的優化,二者性能也不分伯仲,但是重入鎖是可以完全替代synchronized關鍵字的。除此之外,重入鎖又自帶一系列高逼格UBFF:可中斷響應、鎖申請等待限時、公平鎖。另外可以結合Condition來使用,使其更是逼格滿滿。
先來盤花生米:
package somhu;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest implements Runnable{
? ? public static ReentrantLock lock = new ReentrantLock();
? ? public static int i = 0;
? ? @Override
? ? public void run() {
? ? ? ? for (int j = 0; j < 10000; j++) {
? ? ? ? ? ? lock.lock(); ?// 看這里就可以
? ? ? ? ? ? //lock.lock(); ①
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? i++;
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.unlock(); // 看這里就可以
? ? ? ? ? ? ? ? //lock.unlock();②
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? ReentrantLockTest test = new ReentrantLockTest();
? ? ? ? Thread t1 = new Thread(test);
? ? ? ? Thread t2 = new Thread(test);
? ? ? ? t1.start();t2.start();
? ? ? ? t1.join(); t2.join(); // main線程會等待t1和t2都運行完再執行以后的流程
? ? ? ? System.err.println(i);
? ? }
}
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
從上可以看出,使用重入鎖進行加鎖是一種顯式操作,通過何時加鎖與釋放鎖使重入鎖對邏輯控制的靈活性遠遠大于synchronized關鍵字。同時,需要注意,有加鎖就必須有釋放鎖,而且加鎖與釋放鎖的分數要相同,這里就引出了“重”字的概念,如上邊代碼演示,放開①、②處的注釋,與原來效果一致。
硬菜來了:
1、中斷響應
對于synchronized塊來說,要么獲取到鎖執行,要么持續等待。而重入鎖的中斷響應功能就合理地避免了這樣的情況。比如,一個正在等待獲取鎖的線程被“告知”無須繼續等待下去,就可以停止工作了。直接上代碼,來演示使用重入鎖如何解決死鎖:
1
package somhu;
import java.util.concurrent.locks.ReentrantLock;
public class KillDeadlock implements Runnable{
? ? public static ReentrantLock lock1 = new ReentrantLock();
? ? public static ReentrantLock lock2 = new ReentrantLock();
? ? int lock;
? ? public KillDeadlock(int lock) {
? ? ? ? this.lock = lock;
? ? }
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? if (lock == 1) {
? ? ? ? ? ? ? ? lock1.lockInterruptibly(); ?// 以可以響應中斷的方式加鎖
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? Thread.sleep(500);
? ? ? ? ? ? ? ? } catch (InterruptedException e) {}
? ? ? ? ? ? ? ? lock2.lockInterruptibly();
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? lock2.lockInterruptibly(); ?// 以可以響應中斷的方式加鎖
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? Thread.sleep(500);
? ? ? ? ? ? ? ? } catch (InterruptedException e) {}
? ? ? ? ? ? ? ? lock1.lockInterruptibly();
? ? ? ? ? ? }
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? if (lock1.isHeldByCurrentThread()) lock1.unlock(); ?// 注意判斷方式
? ? ? ? ? ? if (lock2.isHeldByCurrentThread()) lock2.unlock();
? ? ? ? ? ? System.err.println(Thread.currentThread().getId() + "退出!");
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? KillDeadlock deadLock1 = new KillDeadlock(1);
? ? ? ? KillDeadlock deadLock2 = new KillDeadlock(2);
? ? ? ? Thread t1 = new Thread(deadLock1);
? ? ? ? Thread t2 = new Thread(deadLock2);
? ? ? ? t1.start();t2.start();
? ? ? ? Thread.sleep(1000);
? ? ? ? t2.interrupt(); // ③
? ? }
}
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
t1、t2線程開始運行時,會分別持有lock1和lock2而請求lock2和lock1,這樣就發生了死鎖。但是,在③處給t2線程狀態標記為中斷后,持有重入鎖lock2的線程t2會響應中斷,并不再繼續等待lock1,同時釋放了其原本持有的lock2,這樣t1獲取到了lock2,正常執行完成。t2也會退出,但只是釋放了資源并沒有完成工作。
2、鎖申請等待限時
可以使用 tryLock()或者tryLock(long timeout, TimeUtil unit) 方法進行一次限時的鎖等待。
前者不帶參數,這時線程嘗試獲取鎖,如果獲取到鎖則繼續執行,如果鎖被其他線程持有,則立即返回 false ,也就是不會使當前線程等待,所以不會產生死鎖。?
后者帶有參數,表示在指定時長內獲取到鎖則繼續執行,如果等待指定時長后還沒有獲取到鎖則返回false。
上代碼:
package somhu;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockTest implements Runnable{
? ? public static ReentrantLock lock = new ReentrantLock();
? ? @Override
? ? public void run() {
? ? ? ? try {
? ? ? ? ? ? if (lock.tryLock(1, TimeUnit.SECONDS)) { // 等待1秒
? ? ? ? ? ? ? ? Thread.sleep(2000); ?//休眠2秒
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? System.err.println(Thread.currentThread().getName() + "獲取鎖失敗!");
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? if (lock.isHeldByCurrentThread()) lock.unlock();
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? TryLockTest test = new TryLockTest();
? ? ? ? Thread t1 = new Thread(test); t1.setName("線程1");
? ? ? ? Thread t2 = new Thread(test); t1.setName("線程2");
? ? ? ? t1.start();t2.start();
? ? }
}
/**
?* 運行結果:
?* 線程2獲取鎖失敗!
?*/?
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
上述示例中,t1先獲取到鎖,并休眠2秒,這時t2開始等待,等待1秒后依然沒有獲取到鎖,就不再繼續等待,符合預期結果。
3、公平鎖
所謂公平鎖,就是按照時間先后順序,使先等待的線程先得到鎖,而且,公平鎖不會產生饑餓鎖,也就是只要排隊等待,最終能等待到獲取鎖的機會。使用重入鎖(默認是非公平鎖)創建公平鎖:
public ReentrantLock(boolean fair) {
? ? sync = fair ? new FairSync() : new NonfairSync();
}
1
2
3
上代碼:
package somhu;
import java.util.concurrent.locks.ReentrantLock;
public class FairLockTest implements Runnable{
? ? public static ReentrantLock lock = new ReentrantLock(true);
? ? @Override
? ? public void run() {
? ? ? ? while (true) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? lock.lock();
? ? ? ? ? ? ? ? System.err.println(Thread.currentThread().getName() + "獲取到了鎖!");
? ? ? ? ? ? } finally {
? ? ? ? ? ? ? ? lock.unlock();
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? FairLockTest test = new FairLockTest();
? ? ? ? Thread t1 = new Thread(test, "線程1");
? ? ? ? Thread t2 = new Thread(test, "線程2");
? ? ? ? t1.start();t2.start();
? ? }
}
/**
?* 運行結果:
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* 線程1獲取到了鎖!
?* 線程2獲取到了鎖!
?* ......(上邊是截取的一段)
?*/
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
可以發現,t1和t2交替獲取到鎖。如果是非公平鎖,會發生t1運行了許多遍后t2才開始運行的情況。
ReentrantLock 配合 Conditond 使用
配合關鍵字synchronized使用的方法如:await()、notify()、notifyAll(),同樣配合ReentrantLock 使用的Conditon提供了以下方法:
public interface Condition {
? ? void await() throws InterruptedException; // 類似于Object.wait()
? ? void awaitUninterruptibly(); // 與await()相同,但不會再等待過程中響應中斷
? ? long awaitNanos(long nanosTimeout) throws InterruptedException;
? ? boolean await(long time, TimeUnit unit) throws InterruptedException;
? ? boolean awaitUntil(Date deadline) throws InterruptedException;
? ? void signal(); // 類似于Obejct.notify()
? ? void signalAll();
}
1
2
3
4
5
6
7
8
9
ReentrantLock 實現了Lock接口,可以通過該接口提供的newCondition()方法創建Condition對象:
public interface Lock {
? ? void lock();
? ? void lockInterruptibly() throws InterruptedException;
? ? boolean tryLock();
? ? boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
? ? void unlock();
? ? Condition newCondition();
}
1
2
3
4
5
6
7
8
上代碼:
package somhu;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockWithConditon implements Runnable{
? ? public static ReentrantLock lock = new ReentrantLock(true);
? ? public static Condition condition = lock.newCondition();
? ? @Override
? ? public void run() {
? ? ? ? lock.newCondition();
? ? ? ? try {
? ? ? ? ? ? lock.lock();
? ? ? ? ? ? System.err.println(Thread.currentThread().getName() + "-線程開始等待...");
? ? ? ? ? ? condition.await();
? ? ? ? ? ? System.err.println(Thread.currentThread().getName() + "-線程繼續進行了");
? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? } finally {
? ? ? ? ? ? lock.unlock();
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? ReentrantLockWithConditon test = new ReentrantLockWithConditon();
? ? ? ? Thread t = new Thread(test, "線程ABC");
? ? ? ? t.start();
? ? ? ? Thread.sleep(1000);
? ? ? ? System.err.println("過了1秒后...");
? ? ? ? lock.lock();
? ? ? ? condition.signal(); // 調用該方法前需要獲取到創建該對象的鎖否則會產生
? ? ? ? ? ? ? ? ? ? ? ? ? ? // java.lang.IllegalMonitorStateException異常
? ? ? ? lock.unlock();
? ? }
}
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
好了,到這里重入鎖ReentrantLock的基本使用方法就介紹完成了!
---------------------?
作者:Somhu?
來源:CSDN?
原文:https://blog.csdn.net/Somhu/article/details/78874634?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的重入锁:ReentrantLock 详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java多线程系列(四)---Reent
- 下一篇: 深入理解ReentrantLock