簡介
AQS是Java併發包中很重要的一個抽象類,我們所使用的ReentrantLock、ReentrantReadWriteLock、CountDownLatch、Semaphore等都是基於AQS來實現的。
1.1 實現原理
AQS中維護了一個state變數這個表示共享的資源,以及一個CHL佇列(多執行緒爭奪資源的時候被阻塞的執行緒將會被放進這個佇列)。這個佇列不需要我們去維護,我們需要關注的幾個點就是state變數的獲取和釋放規則。
state 變數被宣告的型別是volatile型別保證多執行緒下的資源可見性,state=1表示當前資源被佔用。
state值的更新是通過CAS操作來保證它修改的安全性。
AQS中提供了以下主要的方法:
- getState():獲取鎖的標誌state值
- setState():設定鎖的標誌state值
- tryAcquire(int):獨佔方式獲取鎖。嘗試獲取資源,成功則返回true,失敗則返回false。
- tryRelease(int):獨佔方式釋放鎖。嘗試釋放資源,成功則返回true,失敗則返回false。
這裡對應獨佔鎖還有兩個方法是對應共享鎖的 tryShare方法。
private volatile int state;//共享變數,使用volatile修飾保證執行緒可見性
//返回同步狀態的當前值
protected final int getState() {
return state;
}
// 設定同步狀態的值
protected final void setState(int newState) {
state = newState;
}
//原子地(CAS操作)將同步狀態值設定為給定值update如果當前同步狀態的值等於expect(期望值)
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
2.1 一些同步器的實現
ReentrantLock 預設採用非公平鎖,因為考慮獲得更好的效能,通過 boolean 來決定是否用公平鎖(傳入 true 用公平鎖)。
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
public ReentrantLock() {
// 預設非公平鎖
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock 中公平鎖的 lock 方法
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 1. 和非公平鎖相比,這裡多了一個判斷:是否有執行緒在等待
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 方法:
static final class NonfairSync extends Sync {
final void lock() {
// 2. 和公平鎖相比,這裡會直接先進行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 這裡沒有對阻塞佇列進行判斷
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
總結:公平鎖和非公平鎖只有兩處不同:
非公平鎖在呼叫 lock 後,首先就會呼叫 CAS 進行一次搶鎖,如果這個時候恰巧鎖沒有被佔用,那麼直接就獲取到鎖返回了。
非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待佇列是否有執行緒處於等待狀態,如果有則不去搶鎖,乖乖排到後面。
公平鎖和非公平鎖就這兩點區別,如果這兩次 CAS 都不成功,那麼後面非公平鎖和公平鎖是一樣的,都要進入到阻塞佇列等待喚醒。
相對來說,非公平鎖會有更好的效能,因為它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞佇列中的執行緒長期處於飢餓狀態。