ReentrantLock?
synchronized
是託管給JVM執行的,Lock
的鎖定是通過代碼實現
的。所以Lock比較靈活,可以便於開發人員根據合適的場景進行操作,Lock是一個接口,需要實現它來進行使用,ReetrantLock
是Lock的主要實現類,ReetrantLock是一個可重入鎖
,同時可以指定公平鎖
和非公平鎖
,我們來具體看一下他的實現方式。
什麼是可重入鎖?
爲什麼叫重入鎖呢?
ReentrantLock
,我們把它拆開來看就明瞭了。
Re
-Entrant
-Lock
:即表示可重新反覆進入的鎖,但僅限於當前線程;
public void m() {
lock.lock();
lock.lock();
try {
// ... method body
} finally {
lock.unlock()
lock.unlock()
}
}
如示例代碼所示,當前線程可以反覆加鎖
,但也需要釋放同樣加鎖次數
的鎖,即重入了多少次,就要釋放多少次,不然也會導入鎖不被釋放。
試想一下,如果不設計成可重入鎖,那自己如果反覆給自己加鎖,不是會把自己加死鎖了嗎?所以,到現在,重入鎖的概念大概應該清楚了吧?
重入鎖最重要的幾個方法
這幾個方法都是 Lock 接口中定義的:
1. lock()
獲取鎖,有以下三種情況:
- 鎖空閒:直接獲取鎖並返回,同時設置鎖持有者數量爲:1;
- 當前線程持有鎖:直接獲取鎖並返回,同時鎖持有者數量遞增1;
- 其他線程持有鎖:當前線程會休眠等待,直至獲取鎖爲止;
2. lockInterruptibly()
獲取鎖,邏輯和 lock() 方法一樣,但這個方法在獲取鎖過程中能響應中斷。
3. tryLock()
從關鍵字字面理解,這是在嘗試獲取鎖,獲取成功返回:true,獲取失敗返回:false, 這個方法不會等待,有以下三種情況:
- 鎖空閒:直接獲取鎖並返回:true,同時設置鎖持有者數量爲:1;
- 當前線程持有鎖:直接獲取鎖並返回:true,同時鎖持有者數量遞增1;
- 其他線程持有鎖:獲取鎖失敗,返回:false;
4)tryLock(long timeout, TimeUnit unit)
邏輯和 tryLock() 差不多,只是這個方法是帶時間的。
5)unlock()
釋放鎖,每次鎖持有者數量遞減 1,直到 0 爲止。所以也就是爲什麼 lock 多少次,就要對應 unlock 多少次。
6)newCondition
返回一個這個鎖的 Condition 實例,可以實現 synchronized 關鍵字類似 wait/ notify 實現多線程通信的功能,不過這個比 wait/ notify 要更靈活,更強大!
代碼示例:
最簡單的加鎖
private static void add() {
reentrantLock.lock();
try {
count++;
} finally {
reentrantLock.unlock();
}
}
加鎖和釋放鎖都在方法裏面進行,可以自由控制,比 synchronized 更靈活,更方便。但要注意的是,釋放鎖操作必須在 finally 裏面,不然如果出現異常導致鎖不能被正常釋放,進而會卡死後續所有訪問該鎖的線程。
ReentrantLock
和Condition
組件搭配使用:
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
@Slf4j
public class LockExample6 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
new Thread(() -> {
try {
reentrantLock.lock(); // 此時被加入到了AQS等待序列中
log.info("wait signal"); // 1
condition.await(); // 又被AQS移除了(await操作釋放了鎖) 進入了Condition的等待隊列中
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("get signal"); // 4 獲得信號
reentrantLock.unlock();
}).start();
new Thread(() -> {
reentrantLock.lock(); // 獲得鎖 進入AQS等待序列中
log.info("get lock"); // 2
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition.signalAll(); // 發送信號喚醒所有等待的線程 將Condition等待隊列中的線程1節點的 取出並加入到AQS等待隊列中
log.info("send signal ~ "); // 3
reentrantLock.unlock();
}).start();
}
}