ReentrantLock公平鎖的測試代碼如下:
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock(true);//創建一個公平鎖
lock.lock();
try{
TimeUnit.SECONDS.sleep(1);
}catch (Exception e){
}finally{
lock.unlock();
}
}
ReentrantLock的構造函數:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
當參數fair爲true時則創建FairSync類,當fair爲false時創建NonfairSync類
接下來進入lock.lock()方法查看具體獲取鎖的邏輯:
public void lock() {
sync.lock();
}
查看sync的lock()方法可以發現,sync類中的lock()方法爲一個鉤子方法,因爲上面創建的是公平鎖,所以此時具體的獲取鎖資源的實現是在FairSync類中。
FairSync類的lock()方法
final void lock() {
acquire(1);
}
AQS的acquire(int arg)方法是模板方法,公平模式和非公平模式都是用同樣的流程
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
嘗試獲取鎖資源該方法的邏輯大致如下:
- 判斷當前鎖資源是否被某個線程佔用
- 如果鎖資源已經被佔用,則判斷佔用鎖資源的線程是否是當前線程,如果是當前線程,則直接對AQS的狀態變量加acquires,表示重入
- 如果當前鎖資源沒有被佔用,判斷AQS隊列中優先級最高的節點節點所對應的線程是否是當前線程
- 如果是當前線程,則獲取鎖成功
- 如果不是當前線程,意味着AQS隊列中有更高優先級的線程,於是將當前線程封裝成一個Node節點,並將這個節點添加到AQS同步隊列的隊尾
- 阻塞當前線程,等待後續被喚醒
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();//獲取當前線程
int c = getState();//獲取AQS中狀態變量的值
if (c == 0) {//c == 0表示當前沒有以其他線程佔用鎖資源
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果佔用鎖資源的線程是當前線程
int nextc = c + acquires;//AQS狀態變量值+1
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
//判斷當前線程節點在AQS同步隊列中是否還有前驅節點
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
-
h != t 表示AQS的同步隊列中至少有兩個節點
-
(s = h.next) == null 處理的是一種中間狀態
線程節點入隊的方法如上圖所示:
1、先將尾節點設置爲當前節點的prev
2、cas設置隊列的尾節點
3、將舊爲節點的next指向當前節點
(s = h.next) == null ,就是用來處理當前有一個其他線程剛好執行到上面的步驟2,但還沒來得及執行步驟3的情況,此時將會返回true,因爲當前線程不是隊列中優先級最高的線程。
- s.thread != Thread.currentThread() 判斷隊列中第二個節點所維護的線程是否是當前線程
其餘封裝線程爲一個節點並添加到AQS同步隊列的邏輯通非公平模式相同
//將當前線程封裝成一個Node節點,並添加到AQS的同步隊列中
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();//獲取當前節點的前驅節點
if (p == head && tryAcquire(arg)) {//如果前驅節點是head節點,則嘗試獲取鎖資源
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}