一、概念理解
公平鎖:申請所的時候排隊,誰也不插隊
非公平鎖:申請的時候插隊(先插隊,不行了再排隊)
二、差別
ReentrantLock的公平鎖與非公平鎖的差別在於,內部的同步器不一樣,lock()方法調用的是sync的lock(),分別是FairSync與NonfairSync。
sync的lock()內部調用了AQS的acquire,而AQS的acquire做了三件事,第一件是直接申請鎖(tryAcquire),第二件是沒申請下鎖的話,就創建線程等待節點並添加到等待隊列(addWaiter),第三件是將改節點的prev節點標記爲SIGNAL,並掛起該線程,等待喚醒再申請鎖(acquireQueued)。
FairSync與NonfairSync的區別在tryAcquire方法不同,如下代碼及註釋(文末有lock流程圖,一眼看差別)
公平鎖同步器
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//狀態等於0,鎖沒有佔用
if (c == 0) {
//當前隊列無等待線程
if (!hasQueuedPredecessors() &&
//修改鎖狀態成功
compareAndSetState(0, acquires)) {
//設置當前佔着鎖的線程爲當前線程
setExclusiveOwnerThread(current);
//獲取鎖成功
return true;
}
}
//狀態不爲0,鎖被佔用,佔用鎖的線程是當前線程(重入)
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded”);
//疊加狀態(狀態爲記錄當前線程佔用鎖的次數)
setState(nextc);
//獲取鎖成功
return true;
}
//獲取鎖失敗
return false;
}
}
非公平鎖同步器
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
//直接修改鎖狀態爲1,也就是先嚐試申請一下看看能否成功
if (compareAndSetState(0, 1))
//設置當前佔着鎖的線程爲當前線程,申請鎖成功
setExclusiveOwnerThread(Thread.currentThread());
else
//acquire方法會調用tryAcquire
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//狀態等於0,鎖沒有佔用
if (c == 0) {
//修改鎖狀態爲1
if (compareAndSetState(0, acquires)) {
//設置當前佔着鎖的線程爲當前線程,申請鎖成功
setExclusiveOwnerThread(current);
return true;
}
}
//狀態不爲0,鎖被佔用,佔用鎖的線程是當前線程(重入)
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;
}
ps:hasQueuedPredecessors方法
((s = h.next) == null || s.thread != Thread.currentThread()) 是爲了過濾掉兩種臨界情況
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//h != t 這是必要條件
return h != t &&
//h.next == null 說明head存在,tail還未設置,該種情況出現在addWaiter添加第一個等待節點時出現,設置完head還未來得及設置tail
//s.thread != Thread.currentThread() 該情況是防止,當前線程剛被unpark,首次執行tryAcquire時,head還未替換成當前線程節點
((s = h.next) == null || s.thread != Thread.currentThread());
}
三、 白話總結
- 公平鎖在申請的時候先看有線程在排隊沒,有的話就去排隊,沒有的話就申請鎖(所有申請鎖線程都滿足FIFO)。
- 非公平鎖在申請的時候,先插隊看看(也就是直接申請鎖),可以的話就申請成功,不行的話再去排隊,排上隊的線程獲取鎖順序就不會變了(排上隊的線程滿足FIFO)。
附件:
非公平鎖 lock流程圖
公平鎖 lock流程圖
unlock流程