事先聲明 看zejian博客:併發專題 受益良多
https://blog.csdn.net/javazejian/article/category/6940462
synchronized回顧
前面已經介紹了synchronized的原理,JVM隱式的維護對象監視器達到線程安全的目的
針對對象監視器,jvm維護了一些值用於處理同步,如下:
結構 | 名稱 | 說明 |
---|---|---|
_entrySet | 新人隊列 | 試圖調用synchronized代碼 monitorEntry |
_owner | 監視器的所有者 | 擁有對象監視器的線程 |
count | 監視器狀態 | 默認0 有線程進入則+1,重入再+1; 出重入的部分-1,線程出-1 |
_WaitSet | 等待隊列 | 調用wait之類的方法則阻塞線程進入waitSet |
整個流程就如下圖,進入entrySet,沒有線程佔用監視器count==0 或者佔有的是線程就是當前線程,則acquire獲取到資源 count++,否則線程就是掛起狀態 同樣waitSet隊列也具有和entrySet同樣的競爭機制,可以對比AQS的非公平鎖
Lock接口使用
Lock lock = new ReentrantLock();
lock.lock();//相當於synchronized的字節碼monitorentery 上鎖
try{
//臨界區......
}finally{
lock.unlock();//相當於synchronized的字節碼monitorExit 解鎖
}
很明顯的,lock使用的是顯示的上鎖和解鎖,手動調用的 而synchronized是字節碼層面的隱式操作
當然lock接口還有其他synchronzied所不具有的其他功能,比如
//嘗試非阻塞獲取鎖,調用該方法後立即返回結果,如果能夠獲取則返回true,否則返回false
boolean tryLock();
//獲取等待通知組件,該組件與當前鎖綁定,當前線程只有獲得了鎖
//才能調用該組件的wait()方法,而調用後,當前線程將釋放鎖。 可以實現線程間通信
Condition newCondition();
ReentrantLock可重入鎖實現類
Lock的實現類較多,比如讀寫鎖 可重入鎖 其核心原理相同,以可重入鎖爲例
//直接委託sync實現的
public void lock() {
sync.lock();
}
public void unlock() {
sync.release(1);
}
//sync是AQS的抽象子類 有NonfairSync/FairSync非公平/公平鎖兩個子類
abstract static class Sync extends AbstractQueuedSynchronizer{
final boolean nonfairTryAcquire(int acquires){
//嘗試獲取資源 省略代碼
return true;
}
final boolean tryRelease(int releases){
//嘗試釋放資源 省略代碼
return true;
}
}
//非公平鎖
static final class NonfairSync extends Sync {
//如果沒有其他線程搶 當前線程直接就上了 有人搶了就去正常等待獲取
//也就是會把當前線程和等待的線程一塊競爭 這就是公平和非公平的最大區別
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//核心方法 位於AbstractQueuedSynchronizer(AQS)中
}
}
//公平鎖
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//獲取資源
final void lock() {
acquire(1);
}
/**
* 嘗試獲取資源時會盡量保證獲取資源的線程是等待隊列中的第一個線程
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
}
我們看到lock方法實際上是調用AbstractQueuedSynchronizer中的acquire方法,所以我們看看這個AQS
併發基礎組件AQS
AbstractQueuedSynchronizer又稱爲隊列同步器(後面簡稱AQS),它是用來構建鎖或其他同步組件的基礎框架,內部通過一個int類型的成員變量state來控制同步狀態,當state=0時,則說明沒有任何線程佔有共享資源的鎖,當state=1時,則說明有線程目前正在使用共享變量,其他線程必須加入同步隊列進行等待,AQS內部通過內部類Node構成FIFO的同步隊列來完成線程獲取鎖的排隊工作,同時利用內部類ConditionObject構建等待隊列,當Condition調用wait()方法後,線程將會加入等待隊列中,而當Condition調用signal()方法後,線程將從等待隊列轉移動同步隊列中進行鎖競爭。注意這裏涉及到兩種隊列,一種的同步隊列,當線程請求鎖而等待的後將加入同步隊列等待,而另一種則是等待隊列(可有多個),通過Condition調用await()方法釋放鎖後,將加入等待隊列。關於Condition的等待隊列我們後面再分析,這裏我們先來看看AQS中的同步隊列模型,如下
/**
* AQS抽象類
*/
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer{
//指向同步隊列隊頭
private transient volatile Node head;
//指向同步的隊尾
private transient volatile Node tail;
//同步狀態,0代表鎖未被佔用,1代表鎖已被佔用 (類似synchronized的count)
private volatile int state;
//非共享獲取資源
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//死循環的去嘗試獲取資源
selfInterrupt();
}
//非共享釋放資源
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
//省略其他代碼......
}
理解了synchronized之後,在看AQS 發現還是有很多相似之處的,
新線程嘗試獲取資源,獲取不到則放入Node節點隊列中,然後一直死循環嘗試獲取資源(當線程變成頭節點就獲取到資源了),當然會在死循環過程中檢查線程的狀態是否異常
Node節點隊列的操作都是CAS操作來確保線程安全的
AQS作爲抽象類,並不希望被直接調用 而是希望做爲一個基礎組件,而對象資源是否能不獲取到,由子類自己實現
try開頭的方法,便是子類需要實現的,而實現的方式一般是對state的值進行判斷處理 後面再單獨介紹
Lock VS Synchronized Ab
AbstractQueuedSynchronizer通過構造一個基於阻塞的CLH隊列容納所有的阻塞線程,而對該隊列的操作均通過Lock-Free(CAS)操作,但對已經獲得鎖的線程而言,ReentrantLock實現了偏向鎖的功能。
synchronized的底層也是一個基於CAS操作的等待隊列,但JVM實現的更精細,把等待隊列分爲ContentionList和EntryList,目的是爲了降低線程的出列速度;當然也實現了偏向鎖,從數據結構來說二者設計沒有本質區別。但synchronized還實現了自旋鎖,並針對不同的系統和硬件體系進行了優化,而Lock則完全依靠系統阻塞掛起等待線程。
當然Lock比synchronized更適合在應用層擴展,可以繼承AbstractQueuedSynchronizer定義各種實現,比如實現讀寫鎖(ReadWriteLock),公平或不公平鎖;同時,Lock對應的Condition也比wait/notify要方便的多、靈活的多。