1. 程式人生 > >公平鎖與非公平鎖

公平鎖與非公平鎖

bstr gets ado tex input ret trac 對象鎖 ctrl

前言
最近開始讀JDK源碼,所有心得準備總結成一個專欄,JDK Analysis系列的第一篇,就從萬眾矚目的ReentrantLock開始吧,而談到ReentrantLock,就不得不說AQS,它是AbstractQueuedSynchronizer類的簡稱,Doug Lea上神在JDK1.5將其引入,這才有了現在的並發包java.util.concurrent,所以要理解ReentrantLock的原理,AQS也是必須要搞懂的。這篇就先闡述ReentrantLock最基本的公平鎖和非公平鎖的實現,以及部分涉及的AQS原理,AQS源碼解讀將在後續跟進。整個系列基於JDK1.8.0_92。

公平鎖與非公平鎖
大家都知道,在JDK1.5之前,我們在多線程的環境下要想保證線程安全,就必須要使用synchronized關鍵字來實現對象鎖或者類鎖,以此滿足這樣的需求,JDK1.5之後則使用Lock來實現更加細粒度的鎖。在剛接觸Java的時候,學到這兩種方式的時候,粗略地知道後者更加貼近面向對象的思想,但是在工作中遇到一些奇奇怪怪的需求的時候,只是知道這個是遠遠不夠的。synchronized其實是一種公平鎖,所謂公平鎖,就是線程按照執行順序排成一排,依次獲取鎖,但是這種方式在高並發的場景下極其損耗性能;這時候,Lock帶著非公平鎖應運而生了,所謂非公平鎖,就是不管執行順序,每個線程獲取鎖的幾率都是相同的,獲取失敗了,才會采用像公平鎖那樣的方式。這樣做的好處是,JVM可以花比較少的時間在線程調度上,更多的時間則是用在執行邏輯代碼裏面。

公平鎖、非公平鎖的創建方式:

//創建一個非公平鎖,默認是非公平鎖
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);

//創建一個公平鎖,構造傳參true
Lock lock = new ReentrantLock(true);
相關源碼:

public ReentrantLock() {
sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
這部分源碼比較簡單,這裏對於源碼就不贅述。

NonfairSync 非公平鎖
在談NonfairSync之前,首先要談談ReentrantLock類裏面定義的一個類屬性Sync,它才是ReentrantLock實現的精髓。它首先在屬性裏聲明,然後以抽象靜態內部類的形式實現了AQS,源碼如下:

abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;

//聲明的lock()方法,供子類實現
abstract void lock();

//非公平鎖的獲取方式,相較於公平鎖的tryAcquire()
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;
}

//釋放鎖
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}

//判斷當前線程是否是鎖的持有者
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}

final ConditionObject newCondition() {
return new ConditionObject();
}

//獲取當前鎖持有線程,如果在隊列中等待獲取鎖,則返回null
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}

//返回當前線程status的狀態,如果持有鎖就讀取status,沒有就0
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}

//是否上鎖
final boolean isLocked() {
return getState() != 0;
}

/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state

此後,NonfairSync繼承它來實現非公平鎖,FairSync繼承它來實現公平鎖,AQS提供一個tryAcquire()的模板方法來使得公平鎖和非公平鎖的實現方式顯得靈活。我們來看看NonfairSync的源碼:

static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;

final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}

protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);

如代碼所示,在lock的時候,先是嘗試將AQS的status從0設為1,成功的話就把當前線程設置為鎖的持有者,如果嘗試失敗了,基於模板方法,實際調用的是Sync的nonfairTryAcquire(int acquires)方法,該方法源碼如下:

final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState(www.fencaiyule.cn);
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//ReentrantLock是可重入鎖是這裏實現的
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");

首先獲取當前線程,和當前AQS維護的鎖的狀態,如果狀態為0,則嘗試將AQS的status從0設為acquires(實際是1),如果設置成功,則獲取鎖成功,把當前鎖設置為鎖的持有者,返回true;如果當前線程已經是鎖的持有者,則把status+acquires,如果結果越界,拋出異常,如果成功,返回true。細心的同學可以發現,一共有兩次原子設status從0到1,為什麽呢?因為這樣可以提高獲取鎖的概率,因為是非公平的,所以有必要進行這樣的操作,而且這樣的操作與鎖相對來講損耗微乎其微。

FairSync 公平鎖
公平鎖就是每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中獲取,下面是FairSync 的源碼:

static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;

final void lock() {
acquire(1);
}

protected final boolean tryAcquire(int www.8555388.cn/ 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;
}

來看子類的 tryAcquire方法,與非公平鎖比較,獲取鎖的操作只有一點不同,就是加入了hasQueuedPredecessors() 方法,該方法又大有來頭,ctrl進去是這的:

public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
//head沒有next ----> false
//head有next,next持有的線程不是當前線程 ----> true
//head有next,next持有的線程是當前線程 ----> false
return h != t && ((s = h.next) == null |www.tianhengyl1.com | s.thread != Thread.currentThread());

該方法的簽名是:查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間。在AQS中對線程是做了一個FIFO隊列,這裏的tail是尾,head是頭,具體的實現會在後續跟進,這裏就不多做贅述,有意思的是return那一行,其中的意思在上面做了解答,查詢是否有其他線程比當前線程等待獲取鎖花費了更多的時間,有就返回true,沒有就返回false,也就是說該方法返回false,才進行addWaiter狀態的更改嘗試,其余和部分和非公平鎖的部分一樣。

ctrl點進acquire(1)是這樣的:

public final void acquire(int arg) {
if (!tryAcquir www.rbuluoyl.cn e(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
首先通過tryAcquire方法嘗試獲取鎖,如果成功直接返回,否則通過acquireQueued()再次嘗試獲取。在acquireQueued()中會先通過addWaiter將當前線程加入到CLH隊列的隊尾,在CLH隊列中等待。在等待過程中線程處於休眠狀態,直到成功獲取鎖才會返回。

公平鎖與非公平鎖