在上一篇文章中,我們介紹了ReentrantLock類的一些基本用法,今天我們重點來介紹一下ReentrantLock其它的常用方法,以便對ReentrantLock類的使用有更深入的理解。 ...
一、簡介
在上一篇文章中,我們介紹了ReentrantLock
類的一些基本用法,今天我們重點來介紹一下ReentrantLock
其它的常用方法,以便對ReentrantLock
類的使用有更深入的理解。
二、常用方法介紹
2.1、構造方法
ReentrantLock
類有兩個構造方法,核心源碼內容如下:
/**
* 預設創建非公平鎖
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* fair為true表示是公平鎖,fair為false表示是非公平鎖
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
相比於synchronized
同步鎖,ReentrantLock
有一個很大的特點,就是開發人員可以手動指定採用公平鎖機制還是非公平鎖機制。
公平鎖:顧名思義,就是每個線程獲取鎖的順序是按照線程排隊的順序來分配的,最前面的線程總是最先獲取到鎖。
- 優點:所有的線程都有機會得到資源
- 缺點:公平鎖機制實現比較複雜,程式流程比較多,執行速度比較慢
非公平鎖:每個線程獲取鎖的順序是隨機的,並不會遵循先來先得的規則,任何線程在某時刻都有可能直接獲取並擁有鎖,之前介紹的synchronized
其實就是一種非公平鎖
- 優點:公平鎖機制實現相對比較簡單,程式流程比較少,執行速度比較快
- 缺點:有可能某些線程一直拿不到鎖,導致餓死
ReentrantLock
預設的構造方法是非公平鎖,如果想要構造公平鎖機制,只需要傳入true
就可以了。
示例代碼如下:
public static void main(String[] args) {
// 創建公平鎖實現機制
Lock lock = new ReentrantLock(true);
// 創建5個線程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 啟動了!");
// 嘗試獲取鎖
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 獲得鎖!");
} finally {
lock.unlock();
}
}
}).start();
}
}
運行一下程式,結果如下:
ThreadName:Thread-0, 啟動了!
ThreadName:Thread-1, 啟動了!
ThreadName:Thread-0, 獲得鎖!
ThreadName:Thread-1, 獲得鎖!
ThreadName:Thread-2, 啟動了!
ThreadName:Thread-2, 獲得鎖!
ThreadName:Thread-3, 啟動了!
ThreadName:Thread-3, 獲得鎖!
ThreadName:Thread-4, 啟動了!
ThreadName:Thread-4, 獲得鎖!
從日誌上可以看到,啟動順序為0,1,2,3,4
,獲取鎖的順序為0,1,2,3,4
,啟動與獲取鎖的排隊機制一致。
假如我們構造方法裡面的把true
改成false
,也就是非公平鎖機制,在看看運行效果,結果如下:
ThreadName:Thread-1, 啟動了!
ThreadName:Thread-2, 啟動了!
ThreadName:Thread-1, 獲得鎖!
ThreadName:Thread-0, 啟動了!
ThreadName:Thread-2, 獲得鎖!
ThreadName:Thread-3, 啟動了!
ThreadName:Thread-3, 獲得鎖!
ThreadName:Thread-0, 獲得鎖!
ThreadName:Thread-4, 啟動了!
ThreadName:Thread-4, 獲得鎖!
從日誌上可以看到,啟動順序為1,2,0,3,4
,獲取鎖的順序為1,2,3,0,4
,線程啟用與獲取鎖的順序不一致。
從實際的運行結果看,非公平鎖要比公平鎖執行速度要快一些,當線程數越多的時候,效果越明顯。
2.2、核心方法
ReentrantLock
類的核心方法就比較多了,如下表!
方法 | 描述 |
---|---|
public void lock() | 阻塞等待獲取鎖;不允許Thread.interrupt 中斷,即使檢測到Thread.isInterrupted 一樣會繼續嘗試 |
public void lockInterruptibly() | 當前線程未被中斷,則獲取鎖;允許在等待時由其它線程調用等待線程的Thread.interrupt 方法來中斷等待線程的等待而直接返回 |
public boolean tryLock() | 嘗試申請一個鎖,在成功獲得鎖後返回true ,否則,立即返回false |
public boolean tryLock(long timeout, TimeUnit unit) | 在一段時間內嘗試申請一個鎖,在成功獲得鎖後返回true ,否則,立即返回false |
public void unlock() | 釋放鎖 |
public Condition newCondition() | 條件實例,用於線程等待/通知模式 |
public int getHoldCount() | 獲取當前線程持有此鎖的次數 |
public boolean isHeldByCurrentThread() | 檢測是否被當前線程持有 |
public boolean isLocked() | 查詢此鎖是否由任意線程持有 |
public final boolean isFair() | 如果是公平鎖返回true ,否則返回false |
public final boolean hasQueuedThreads() | 查詢是否有線程正在等待 |
public final boolean hasQueuedThread(Thread thread) | 查詢給定線程是否正在等待獲取此鎖 |
public final int getQueueLength() | 獲取正等待獲取此鎖的線程數 |
public boolean hasWaiters(Condition condition) | 是否存在正在等待並符合相關給定條件的線程 |
public int getWaitQueueLength(Condition condition) | 正在等待並符合相關給定條件的線程數量 |
雖然方法很多,但是實際上常用方法就那麼幾個,下麵我們主要抽一些常用的方法進行介紹。
2.2.1、tryLock 方法
lock()
、lockInterruptibly()
、tryLock()
和tryLock(long timeout, TimeUnit unit)
這幾個方法,目的其實是一樣的,都是為了獲取鎖,只是針對不同的場景做了單獨的處理。
lock()
:阻塞等待獲取鎖,如果沒有獲取到會一直阻塞,即使檢測到Thread.isInterrupted
一樣會繼續嘗試;
lockInterruptibly()
:同樣也是阻塞等待獲取鎖,稍有不同的是,允許在等待時由其它線程調用等待線程的Thread.interrupt
方法來中斷等待線程的等待而直接返回tryLock()
:表示嘗試申請一個鎖,在成功獲得鎖後返回true
,否則,立即返回false
,不會阻塞等待獲取鎖tryLock(long timeout, TimeUnit unit)
:表示在一段時間內嘗試申請一個鎖,在成功獲得鎖後返回true
,否則,立即返回false
其中tryLock(long timeout, TimeUnit unit)
方法的應用最廣泛,因為它能防止程式發生死鎖,即使在一段時間內沒有獲取鎖,也會自動退出,不會一直阻塞。
我們可以看一個簡單的例子,如下!
public static void main(String[] args) {
// 創建公平鎖實現機制
Lock lock = new ReentrantLock();
// 創建5個線程
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
boolean flag = false;
try {
// 嘗試3秒內獲取鎖
flag = lock.tryLock(3, TimeUnit.SECONDS);
if(flag){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 獲取到鎖");
// 模擬進行5秒的業務操作
Thread.sleep(5000);
} else {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (flag){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 釋放對象");
lock.unlock();
}
}
}
}).start();
}
}
運行一下程式,結果如下:
ThreadName:Thread-0, 獲取到鎖
ThreadName:Thread-3, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-1, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-2, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-4, 經過3秒鐘的嘗試未獲取到鎖,放棄嘗試
ThreadName:Thread-0, 釋放對象
可以很清晰的看到,非Thread-0
線程嘗試了 3 秒沒有獲取到鎖,自動放棄;如果換成lock()
方法進行獲取鎖,線程Thread-0
如果不釋放鎖,其它線程會一直阻塞。
2.2.2、unlock 方法
unlock()
方法也是常用方法,表示釋放鎖。當獲取到鎖之後,一定要手動釋放鎖,否則可能會造成其它程式執行出現問題,通常用在finally
方法塊裡面。
// 阻塞等待獲取鎖
lock.lock();
try {
// 業務操作...
} finally {
// 一定要釋放鎖
lock.unlock();
}
2.2.3、newCondition 方法
newCondition()
方法,在上文中介紹過,ReentrantLock
和Condition
結合,可以實現線程之間的等待/通知模型。
簡單的示例,如下!
public class Counter {
private final Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private int count;
public void await(){
// 加鎖
lock.lock();
try {
// 讓當前線程進入等待狀態,並釋放鎖
condition.await();
System.out.println("await等待結束,count:" + getCount());
} catch (Exception e){
e.printStackTrace();
} finally {
// 釋放鎖
lock.unlock();
}
}
public void signal(){
// 加鎖
lock.lock();
try {
count++;
// 喚醒某個等待線程
condition.signal();
System.out.println("signal 喚醒通知完畢");
} catch (Exception e){
e.printStackTrace();
} finally {
// 釋放鎖
lock.unlock();
}
}
public int getCount() {
return count;
}
}
public class MyThreadTest {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
// 先啟動執行等待的線程
new Thread(new Runnable() {
@Override
public void run() {
counter.await();
}
}).start();
Thread.sleep(3000);
// 過3秒,再啟動執行通知的線程
new Thread(new Runnable() {
@Override
public void run() {
counter.signal();
}
}).start();
}
}
運行一下程式,結果如下:
signal 喚醒通知完畢
await等待結束,count:1
2.2.4、getHoldCount 方法
getHoldCount()
方法的作用是返回的是當前線程調用lock()
的次數。
示例代碼如下:
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
new Thread(new Runnable() {
@Override
public void run() {
// 第一次獲取鎖
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" + lock.getHoldCount());
// 第二次獲取鎖
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", getHoldCount:" + lock.getHoldCount());
} finally {
lock.unlock();
}
} finally {
lock.unlock();
}
}
}).start();
}
運行一下程式,結果如下:
ThreadName:Thread-0, getHoldCount:1
ThreadName:Thread-0, getHoldCount:2
側面也證明瞭一點,ReentrantLock
和synchronized
一樣,鎖都具有可重入特性,也就是說同一個線程多次調用同一個ReentrantLock
的lock()
方法,可以再次進入方法體,無需阻塞等待。
2.2.5、isLocked 方法
isHeldByCurrentThread()
和isLocked()
方法都是用於檢測鎖是否被持有。
其中isHeldByCurrentThread()
方法表示此鎖是否由當前線程持有;isLocked()
方法表示此鎖是否由任意線程持有。
我們看一個簡單的示例,如下:
public class Counter {
private ReentrantLock lock = new ReentrantLock();
public void methodA(){
lock.lock();
try {
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 當前線程是否持有鎖:" + lock.isHeldByCurrentThread());
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意線程是否持有鎖:" + lock.isLocked());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void methodB(){
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 當前線程是否持有鎖:" + lock.isHeldByCurrentThread());
System.out.println("ThreadName:" + Thread.currentThread().getName() + ", 任意線程是否持有鎖:" + lock.isLocked());
}
}
public class MyThreadTest {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
new Thread(new Runnable() {
@Override
public void run() {
counter.methodA();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
counter.methodB();
}
}).start();
}
}
運行一下程式,結果如下:
ThreadName:Thread-0, 當前線程是否持有鎖:true
ThreadName:Thread-0, 任意線程是否持有鎖:true
ThreadName:Thread-1, 當前線程是否持有鎖:false
ThreadName:Thread-1, 任意線程是否持有鎖:true
從日誌結果很容易理解,Thread-0
線程持有鎖,因此調用isHeldByCurrentThread()
和isLocked()
方法,返回結果都是true
;Thread-1
線程沒有持有鎖,因此isHeldByCurrentThread()
方法返回false
,isLocked()
方法返回true
2.2.6、isFair 方法
isFair()
方法用來獲取此鎖是否公平鎖。
簡單的示例,如下:
ReentrantLock lock = new ReentrantLock(true);
System.out.println("是否公平鎖:" + lock.isFair());
輸出結果如下:
是否公平鎖:true
ReentrantLock
預設的是非公平鎖,當通過構造方法顯式傳入true
時,採用的是公平鎖機制
2.2.5、hasQueuedThreads 方法
hasQueuedThreads()
和hasQueuedThread()
方法都用於查詢是否有線程等待獲取鎖,稍有不同的是:hasQueuedThreads()
方法表示查詢是否有線程正在等待獲取鎖;hasQueuedThread()
方法表示查詢給定線程是否正在等待獲取此鎖。
另外還有一個getQueueLength()
方法,表示獲取正等待獲取此鎖的線程數。
我們看一個簡單的示例,如下:
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadA.start();
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadB.start();
// 等待線程都啟動完畢
Thread.sleep(1000);
System.out.println("查詢是否有線程正在等待:" + lock.hasQueuedThreads());
System.out.println("查詢處於等待的線程數:" + lock.getQueueLength());
System.out.println("threadA 是否處於等待狀態:" + lock.hasQueuedThread(threadA));
System.out.println("threadB 是否處於等待狀態:" + lock.hasQueuedThread(threadB));
}
輸出結果如下:
查詢是否有線程正在等待:true
查詢處於等待的線程數:1
threadA 是否處於等待狀態:false
threadB 是否處於等待狀態:true
從日誌上可以清晰的看到,線程threadA
先獲取了鎖,線程threadB
處於等待獲取鎖的狀態,處於等待的線程數為1
。
2.2.7、hasWaiters 方法
hasWaiters()
和getWaitQueueLength()
方法,支持傳入condition
條件對象進行查詢。
其中hasWaiters()
方法表示查詢是否存在正在等待並符合相關給定條件的線程;getWaitQueueLength()
方法表示查詢正在等待並符合相關給定條件的線程數量。
我們看一個簡單的示例,如下:
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
condition.await();
System.out.println("await等待結束");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadA.start();
// 睡眠1秒
Thread.sleep(1000);
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try {
System.out.println("是否存在正在等待並符合相關給定條件的線程:" + lock.hasWaiters(condition));
System.out.println("正在等待並符合相關給定條件的線程數量:" + lock.getWaitQueueLength(condition));
Thread.sleep(5000);
condition.signal();
System.out.println("signal 喚醒通知完畢");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
});
threadB.start();
}
輸出結果如下:
是否存在正在等待並符合相關給定條件的線程:true
正在等待並符合相關給定條件的線程數量:1
signal 喚醒通知完畢
await等待結束
需要註意的是,調用condition
對象的方法,必須要在獲取鎖的方法體內執行。
三、小結
本文主要圍繞ReentrantLock
類的核心方法進行了一些知識總結,其中最常用方法的主要就兩個,tryLock(long timeout, TimeUnit unit)
和unlock()
,通過它可以實現線程同步安全的效果。
本文內容比較多,如果有不正之處,請多多諒解,並歡迎批評指出。
四、參考
1、https://www.cnblogs.com/xrq730/p/4855538.html
作者:程式員志哥
出處:pzblog.cn
資源:微信搜【程式員志哥】關註我,回覆 【技術資料】有我準備的一線程式必備電腦書籍、大廠面試資料和免費電子書。 希望可以幫助大家提升技術和能力。