在開發過程中,synchronized是最簡單的同步控制方法,在通常情況下是夠用的,但synchronized時不響應中斷,而且有時候,我們需要靈活的來控制加解鎖。這時候可以使用ReentrantLock。
在以前的版本中,synchronized效率是遠遠低於ReentrantLock,後來經過優化,兩者性能差距不大了。但ReentrantLock有一些新特性,是synchronized所不具備的。
1、接口
//Lock.java
/* 加鎖 */
void lock();
//可響應中斷
void lockInterruptibly() throws InterruptedException;
//嘗試加鎖,失敗馬上返回
boolean tryLock();
//嘗試加鎖,包含最大等待時間,最大等待時間範圍內未加鎖,返回。
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解鎖,注意不要忘,如果有異常,解鎖應放在finally中
void unlock();
//獲取鎖的condition對象,然後做await,signal來實現等待和通知
Condition newCondition();
另外ReentrantLock可以選擇是否使用公平鎖,公平鎖需要維持一個有序隊列,按照請求鎖的先後順序來獲得鎖,因而效率較低,但好處在於不會產生飢餓現象,即一個線程,只要等待,必然能獲取到鎖。而非公平鎖則不是這樣,非公平鎖除了隨機獲得鎖以外,還有一個隱藏屬性,即一個線程會傾向於再次獲得已經持有的鎖,這樣的鎖的分配方式比較高效。
再來說說Condition。
//Condition.java
//等待,可響應中斷
void await() throws InterruptedException;
//等待,不響應中斷
void awaitUninterruptibly();
//按照時間來等待,響應中斷
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
//通知
void signal();
void signalAll();
2、原理
ReentrantLock使用內部類Sync來實現加解鎖,Sync是AbstractQueuedSynchronizer(下面簡稱AQS)的子類。
AQS,人如其名,抽象隊列同步器,定義了一套多線程訪問共享資源的同步器框架,是模板模式的典型應用,不光用在ReentrantLock中,也用在Semaphor/CountDownLatch等上。
AQS核心是一個共享資源(volatile int state;)和一個等待隊列。共享資源相關的方法有3個。
//AbstractQueuedSynchronizer.java
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}`
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
隊列是一個FIFO的雙向隊列,隊列相關結構如下。
//註釋
* <pre>
* +------+ prev +-----+ +-----+
* head | | <---- | | <---- | | tail
* +------+ +-----+ +-----+
* </pre>
//code
private transient volatile Node head;
private transient volatile Node tail;
AQS定義了2種資源共享的方式,EXCLUSIVE(獨佔,即只有一個線程能獲取到該資源,比如ReentrantLock),SHARED(共享,多個線程可以共享該資源,Semephore/CountDownLatch),當然也可以兼而有之,比如ReentrantReadWriteLock。
等待隊列的每一項爲Node,核心結構如下
//AbstractQueueSynchronizer.java
//前一個Node和下一個Node
volatile Node prev;
volatile Node next;
//本Node持有的線程
volatile Thread thread;
//指定本Node的模式,標識共享/獨佔
Node nextWaiter;
//標識本Node的各種狀態,比如被中斷,下個節點需要通知,等等等等
volatile int waitStatus;
下面通過具體過程來闡述加鎖的細節。
2-1、加鎖
通常使用的都是非公平鎖,我們以這個爲例來說明
//ReentrantLock.java
private final Sync sync;
public ReentrantLock() {
sync = new NonfairSync();//默認是非公平鎖
}
public void lock() {
sync.lock();
}
//NonfairSync內部類,非公平鎖加鎖執行本方法
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
其中,compareAndSetState(0, 1),使用CAS來設置State爲1,成功返回true,並執行
//AbstractOwnableSynchronizer.java
//設置當前執行的線程
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
此時表示加鎖成功。如果state當時已經爲1,即有其他線程已拿到鎖,則compareAndSetState(0, 1)馬上返回false表示失敗。並執行如下代碼。
//AbstractQueuedSychronizer.java
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
首先,繼續嘗試拿鎖。
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//state爲9表示可以加鎖,馬上CAS操作設置State爲1
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果本線程已經拿到鎖,state也不爲0,此時,state+=1
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;
}
上一步加鎖成功,直接返回,失敗則將本線程進行加隊列操作。
private Node addWaiter(Node mode) {
//創建當前線程的Node,獨佔模式
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//嘗試使用快速方式增加節點到隊尾
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
先使用快速方式增加節點,一次成功可以馬上返回,失敗則執行enq方法。
private Node enq(final Node node) {
//經典的CAS自旋volatile變量
for (;;) {
Node t = tail;
//隊列爲空,創建頭結點,即尾節點
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
//CAS加入隊尾,成功返回,失敗繼續自旋
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
加隊列成功後,可以以等待狀態休息了,直到其他線程釋放資源後喚醒本線程,下面來看源碼
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//記錄是否被中斷
boolean interrupted = false;
//自旋拿鎖,即拿state
for (;;) {
//隊列前一個節點
final Node p = node.predecessor();
//如果前一個節點是head節點,則可以嘗試進行拿鎖,即CAS設置state,快速返回成功或失敗。
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//除非上面作爲第二個節點拿到鎖返回了,其他情況執行下面代碼
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果本線程的Node作爲隊列中第二個節點拿到鎖成功,否則會執行shouldParkAfterFailedAcquire,該函數其實是將Node往前插隊,前面的Node可能因爲各種原因已經死去(中斷等等),直到找到確實在等待拿鎖的,然後通過park進入waiting狀態。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;//前驅的狀態
if (ws == Node.SIGNAL)
//如果已經告訴前驅拿到鎖通知自己了,直接返回,可以馬上休息
return true;
if (ws > 0) {
//其他情況,循環遍歷隊列,如果前驅放棄了,就繼續往前找,直到找到正常的節點,並排在他後面。那些被放棄的節點,由於引用消失,後續會被GC掉。
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
//前驅狀態正常,就設置前驅狀態爲SIGNAL,表示,前驅拿到鎖後通知自己
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
上述函數如果返回成功,則執行park,直到被unpark或中斷喚醒。這裏特意注意下,如果park時收到中斷,並不會拋異常,而是通過Thread.interrupted()獲得中斷標記,並清除掉中斷標記。
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
如果是中斷,會在acquire中執行中斷,整個拿鎖流程如下圖所示
1、入隊-》2、是否二號&拿鎖-》3、找正常前前節點-》4、park等待-》2
2-》成功拿鎖/中斷
公平鎖的區別只有下面的獲取鎖的方法有區別
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//state爲0可以加鎖。hasQueuedPredecessors表示前面是否有Node,具體代碼見下
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;
}
其中hasQueuedPredecessors表示是否前面有節點
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;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
另外還有一些tryLock(只使用樂觀加鎖嘗試一次,直接返回)。 lockInteruptbly(基本流程相同,把最終的Thread.interupt換爲throw InteruptException)
2-2、解鎖
unlock流程如下,執行unlock表示已經拿到lock,因而不需要考慮線程安全的問題,直接將state-1即可,唯一需要注意的是多次lock需要多次unlock,這裏要判斷是否存在未完全釋放資源的情況。
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;//找到頭結點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//通知下一個Node
return true;
}
return false;
}
首先執行tryRelease,每次都把state-=1;
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;
}
再來看unparkSuccessor
private void unparkSuccessor(Node node) {
//清零當前node(頭結點)的waitState,允許失敗
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;//下一個節點
if (s == null || s.waitStatus > 0) {//爲空或取消
s = null;
//從隊列尾部往前找,waitStatus <0即爲有效的節點
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);//對該節點進行unpark喚醒
}
unlock的流程主要在於對下一個等待線程的通知上。
2-3 Condition.await
首先是lock.newCondition()
//ReentrantLock.java
final ConditionObject newCondition() {
return new ConditionObject();
}
ConditionObject是AQS的內部類,主要結構如下
//雙向等待隊列的首節點
/** First node of condition queue. */
private transient Node firstWaiter;
//尾節點
/** Last node of condition queue. */
private transient Node lastWaiter;
關於await,基本猜想就是往上面的隊列中加,然後阻塞等,基本邏輯跟lock換湯不換藥。下面來驗證我們的猜想。下面來看await的代碼
//AbastractQueuedSynchronizer.java
public final void await() throws InterruptedException {
if (Thread.interrupted())//先判斷中斷
throw new InterruptedException();
//加入等待隊列,就是上面ConditionObject中的那個。
Node node = addConditionWaiter();
//釋放鎖將lock等待隊列的下一個節點進行unpark通知
int savedState = fullyRelease(node);
int interruptMode = 0;
//循環判斷是否在AQS的等待隊列中,不在就進行park動作。之後不論是被unpark喚醒,還是中斷,均會跳出次循環。
while (!isOnSyncQueue(node)) {
//這裏的park是conditionObject的this,即只有signal或中斷的會喚醒本線程
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
先看addConditionWaiter的代碼,代碼的意義在於加入等待隊列。
private Node addConditionWaiter() {
Node t = lastWaiter;
//如果尾節點取消了等待,清除隊列上無效的節點
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//本節點添加到隊列
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
看unlinkCancelledWaiters,其實就是遍歷conditionObject的整個隊列,將非等待狀態的節點摘除。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;//頭結點
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
//不是等待節點,就將該節點摘鏈
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
//到尾部了
if (next == null)
lastWaiter = trail;
}
else
trail = t;
//往後遍歷
t = next;
}
}
再看fullyRelease,其實就是unlock操作,並找出lock隊列(AQS的隊列)中下一個節點(下一個狀態不正確, 會從頭結點開始尋找)進行unpark通知
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);//參考unlock流程的unpark,這裏不展開
return true;
}
return false;
}
其中tryRelease取決於何種子類,對於ReentrantLock來說,就是state-1。
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;
}
再回過頭看下一個流程,判斷是否在等待lock的等待隊列中,即AQS的隊列
final boolean isOnSyncQueue(Node node) {
//正常等待condition節點可以進行unpark
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//其他時候需要下一個節點在AQS的等待隊列中,即等待拿鎖lock中,這裏判斷不在lock中,執行下一步動作
if (node.next != null) // If has successor, it must be on queue
return true;
//從尾部往前找,找到本節點返回true
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
回過頭來梳理這個流程,簡單來理解,就是入ConditionObject的隊列進行park等待,直到被喚醒或中斷,這兩種都會跳出while循環。下面來看後面的流程。
這裏剛從await的park中出來,要麼被signal喚醒,要麼被中斷喚醒。下面會重新拿鎖,並返回。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
await的核心其實就是釋放鎖,並通過park等待signal。後面被喚醒時,再拿鎖並返回。
2-4 Condition.signal
signal的核心是將線程從await的等待狀態(park)中喚醒。
public final void signal() {
//當前非本線程執行,拋異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
來看核心流程,通知Condition隊列的頭結點。
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//當前Condition狀態置爲0
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//節點入AQS鎖的等待隊列
Node p = enq(node);
int ws = p.waitStatus;
//設置爲Sinal狀態,並進行unpark
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
signal的效果是將頭結點加入Lock的等待隊列,並通知那個線程啓動。那個線程在執行await()會由於while (!isOnSyncQueue(node)) 而跳出循環。
signalAll會把ConditionObject隊列中的所有節點都移入AQS的等待隊列並喚醒他們。