1. 簡介
在前面的文章中我們介紹了lock接口,AQS同步器,重入鎖,condition接口,這篇文章將準備將以上知識點在從源碼上梳理一遍。
2. Lock
在java5以後,增加了JUC的併發包且提供了Lock接口用來實現鎖的功能,它提供了與synchronized關鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖。雖然它缺少了(通過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性。
Lock是一個接口,核心的兩個方法lock和unlock,下面是Lock接口源碼:
public interface Lock {
//獲取鎖
void lock();
//獲取鎖的過程能夠響應中斷
void lockInterruptibly() throws InterruptedException;
//非阻塞式響應中斷能立即返回,獲取鎖放回true反之返回fasle
boolean tryLock();
//超時獲取鎖,在超時內或者未中斷的情況下能夠獲取鎖
boolean tryLock(long var1, TimeUnit var3) throws InterruptedException;
//釋放鎖
void unlock();
//獲取與lock綁定的等待通知組件,當前線程必須獲得了鎖才能進行等待,進行等待時會先釋放鎖,當再次獲取鎖時才能從等待中返回
Condition newCondition();
}
3. AbstractQueuedSynchronizer(AQS抽象隊列同步器)
AQS是用來構建鎖或者其他同步組件的基礎框架,通過一個int變量來表示當前同步狀態,AQS依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理,當前線程獲取同步狀態失時,AQS會將當前線程以及等待狀態等信息構造成爲一個節點(Node)並將其加入同步隊列,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試獲取同步狀態。
Node屬性如下:
static final class Node {
int waitStatus; //表示節點的5種狀態
Node prev; //前繼節點
Node next; //後繼節點
Node nextWaiter; //存儲在condition隊列中的後繼節點
Thread thread; //當前線程
}
其中節點狀態:
- Cancelled,值爲1,由於在同步隊列中等待的線程等待超時或者被中斷,需要從同步隊列中取消等待,節點進入該狀態將不會變化
- Signal,值爲-1,後繼節點的線程處於等待狀態,而當前節點的線程如果釋放了同步狀態或者被取消,將會通知後繼節點,使後繼節點的線程得以運行
- Condition,值爲-2,節點在等待隊列中,節點線程等待在Condition 上,當其他線程對Condition 調用了 signal()方法後,該節點將會從等待隊列中轉移到同步隊列中,加入到對同步狀態的獲取中
- Propagate, 值爲-3, 表示下一次共享式同步狀態獲取將會無條件地被傳播下去
- Initial, 值爲0,初始狀態
4. 從ReentrantLock重入鎖開始
ReentrantLock重入鎖是接口Lock 的一種實現,他支持重進入,除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇。下面是重入鎖的部分源碼:
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
//構造函數,默認是非公平鎖
public ReentrantLock() {
sync = new NonfairSync();
}
//構造函數,可以選擇是否公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
//非公平鎖
static final class NonfairSync extends ReentrantLock.Sync{
.....
}
//公平鎖
static final class FairSync extends ReentrantLock.Sync{
......
}
//繼承AQS(同步器)
abstract static class Sync extends AbstractQueuedSynchronizer {
.....
}
//線程等待隊列
public Condition newCondition() {
return this.sync.newCondition();
}
下面開始一個簡單例子:
private static int count = 0;
static Lock lock = new ReentrantLock();
public static void inc() {
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
lock.unlock();
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 1000; i++) {
new Thread(() -> {
RLDemo.inc();
}).start();
}
Thread.sleep(3000);
System.out.println("result:" + count);
}
在上面的例子中,生成1000個線程,每個線程都調用inc方法實現count+1。這裏補充下,Lock 一般使用static修飾,作爲類的一個屬性。
在類加載的時候發現static修飾的lock 就會初始化new ReentrantLock()。重入鎖的構造函數,就知道他去生成了一個非公平的重入鎖實例,而重入鎖是通過AQS生成的,AQS是繼承AbstractOwnableSynchronizer抽象類。AbstractOwnableSynchronizer只是記錄當前被哪個線程持有。ReentrantLock初始化時序圖如下:
ReentrantLock初始化完成後會有三個屬性:
- Node head ,頭節點來源於AQS中Node
- Node tail 尾點來源於AQS中Node
- int state 同步狀態
- Thread exclusiveOwnerThread 當前持有同步狀態的線程來源於AQS父類中AbstractOwnableSynchronizer
如圖:
lock初始完成後,將會調用其lock方法加鎖,注意lock()、unlock方法是對於鎖的使用者來說,而lock之後是對於鎖的開發者。