ReentrantLock
在處理線程安全問題上,在JDK5以後引入了 Lock ,synchronized和Lock都可以保證線程安全問題!而Lock比synchronized使用更加靈活,也更適合複雜的併發場景。本文主要講解Lock的子類ReentrantLock。
一.ReentrantLock與synchronized 比較
(1)synchronized是獨佔鎖,加鎖和解鎖的過程自動進行,易於操作,但不夠靈活。ReentrantLock也是獨佔鎖,加鎖和解鎖的過程需要手動進行,不易操作,但非常靈活。
(2)synchronized可重入,因爲加鎖和解鎖自動進行,不必擔心最後是否釋放鎖;ReentrantLock也可重入,但加鎖和解鎖需要手動進行,且次數需一樣,否則其他線程無法獲得鎖。
(3)synchronized不可響應中斷,一個線程獲取不到鎖就一直等着;ReentrantLock可以相應中斷。
ReentrantLock好像比synchronized關鍵字沒好太多,我們再去看看synchronized所沒有的,一個最主要的就是ReentrantLock還可以實現公平鎖機制。什麼叫公平鎖呢?也就是在鎖上等待時間最長的線程將獲得鎖的使用權。通俗的理解就是誰排隊時間最長誰先執行獲取鎖。
二.ReentrantLock 原理
這裏我們從源碼進行分析學習,分析過程:
1.ReentrantLock 類結構
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
ReentrantLock 實現了Lock接口,實現了鎖的機制,Sync 源碼對他的描述是同步器提供所有實施機制,Sync 也是ReentrantLock的核心;
2.ReentrantLock 兩種初始化狀態
2.1 非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
2.2 公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平鎖與非公平鎖的最大區別就是公平鎖很儘可能的將等待時間最長的線程,具有最高的執行權重
無論是公平還是非公鎖的實現都是繼承了上面說到的 Sync,話題又回到了Sync;
3.Sync 設計
// 內部類,自定義同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否處於佔用狀態
protected boolean isHeldExclusively() {
}
// 當狀態爲0的時候獲取鎖
public boolean tryAcquire(int acquires) {
}
// 釋放鎖,將狀態設置爲0
protected boolean tryRelease(int releases) {
}
}
從源碼我們可以看出Sync繼承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer也是傳說的AQS,AQS的設計如下圖:
在同步器中就包含了sync隊列。同步器擁有三個成員變量:sync隊列的頭結點head、sync隊列的尾節點tail和狀態state。對於鎖的獲取,請求形成節點,將其掛載在尾部,而鎖資源的轉移(釋放再獲取)是從頭部開始向後進行。對於同步器維護的狀態state,多個線程對其的獲取將會產生一個鏈式的結構。
4.ReentrantLock API
Modifier and Type | Method and Description |
---|---|
int |
getHoldCount() 查詢當前線程對此鎖的暫停數量。 |
protected Thread |
getOwner() 返回當前擁有此鎖的線程,如果不擁有,則返回 null 。 |
protected Collection |
getQueuedThreads() 返回包含可能正在等待獲取此鎖的線程的集合。 |
int |
getQueueLength() 返回等待獲取此鎖的線程數的估計。 |
protected Collection |
getWaitingThreads(Condition condition) 返回包含可能在與此鎖相關聯的給定條件下等待的線程的集合。 |
int |
getWaitQueueLength(Condition condition) 返回與此鎖相關聯的給定條件等待的線程數的估計。 |
boolean |
hasQueuedThread(Thread thread) 查詢給定線程是否等待獲取此鎖。 |
boolean |
hasQueuedThreads() 查詢是否有線程正在等待獲取此鎖。 |
boolean |
hasWaiters(Condition condition) 查詢任何線程是否等待與此鎖相關聯的給定條件。 |
boolean |
isFair() 如果此鎖的公平設置爲true,則返回 true 。 |
boolean |
isHeldByCurrentThread() 查詢此鎖是否由當前線程持有。 |
boolean |
isLocked() 查詢此鎖是否由任何線程持有。 |
void |
lock() 獲得鎖。 |
void |
lockInterruptibly() 獲取鎖定,除非當前線程是 |
Condition |
newCondition() 返回[Condition ]用於這種用途實例[Lock ]實例。 |
String |
toString() 返回一個標識此鎖的字符串以及其鎖定狀態。 |
boolean |
tryLock() 只有在調用時它不被另一個線程佔用才能獲取鎖。 |
boolean |
tryLock(long timeout, TimeUnit unit) 如果在給定的等待時間內沒有被另一個線程 [佔用] ,並且當前線程尚未被 [保留,]則獲取該鎖。 |
void |
unlock() 嘗試釋放此鎖。 |
三.實戰
這裏模擬售票,通過ReentrantLock的方式實現線程的安全
public class LockMain {
public static void main(String[] args) {
Window window = new Window();
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
Thread thread3 = new Thread(window);
thread1.start();
thread2.start();
thread3.start();
}
}
/**
* 售票窗口
*/
class Window implements Runnable{
private volatile int num = 100;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (num > 0){
System.out.println(Thread.currentThread().getName()+"窗口在售票,票號爲"+ num);
num --;
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
添加小編微信:372787553 ,帶你進入技術交流區,記得備註進羣