測試代碼:
https://github.com/kevindai007/springboot_houseSearch/tree/master/src/test/java/com/kevindai/juc
Condition
首先來看下Condition的簡單用法
public class ConditionTest {
public static void main(String[] args) {
final ReentrantLock reentrantLock = new ReentrantLock();
final Condition condition = reentrantLock.newCondition();
new Thread(new Runnable() {
@Override
public void run() {
reentrantLock.lock();
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程1開始運行,等待條件執行");
try {
condition.await();//與wait()一樣,會釋放鎖
System.out.println("線程1恢復執行");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("線程1釋放鎖");
reentrantLock.unlock();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try{
reentrantLock.lock();
condition.signal();
System.out.println("線程2開始運行,釋放條件");
}finally {
System.out.println("線程2釋放鎖");
reentrantLock.unlock();
}
}
}).start();
}
}
由這個例子能看出,Condition的await()方法與Object.wait()方法類似,signal()與Object.notify()類似,其實Condition還有signalAll()方法與Object.notifyAll()類似(用法類似,使用時需要獲取鎖,調用await()時會釋放鎖),下面咱們一起看看Condition的源碼
Condition是一個接口,主要方法如下
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();
這些方法從字面上很容易理解,在此不做分析,下面咱們看看其實現類,先看await()方法
public final void await() throws InterruptedException {
if (Thread.interrupted())//判斷當前線程是否被中斷
throw new InterruptedException();
//將當前線程作爲內容構造的節點node放入到條件隊列中並返回此節點
Node node = addConditionWaiter();
//釋放當前線程所擁有的鎖,返回值爲AQS的狀態位(即此時有幾個線程擁有鎖(考慮ReentrantLock的重入))
int savedState = fullyRelease(node);
int interruptMode = 0;
/*
檢測此節點是否在同步隊列上,如果不在,說明此線程還沒有資格競爭鎖,此線程就繼續掛起;直到檢測到此節點在同步隊列上(在什麼時候加入的呢?在有線程發出signal信號的時候),
*/
while (!isOnSyncQueue(node)) {
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);
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
/*
CONDITION,值爲-2,表示當前節點在等待condition,也就是在condition隊列中;如果此節點的狀態不是CONDITION,則需要將此節點在條件隊列中移除
*/
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;
}
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;
}
}
await方法的大概思想爲:首先將此代表該當前線程的節點加入到條件隊列中去,然後釋放該線程所有的鎖並開始睡眠,最後不停的檢測AQS隊列中是否出現了此線程節點.如果收到signal信號之後就會在AQS隊列中檢測到,檢測到之後,說明此線程又參與了競爭鎖.
注意:這裏提到了兩個隊列,一個是Condition中的隊列,一個是AQS的隊列;
AQS隊列是當前等待資源(這裏的資源就是鎖)的隊列,AQS會在資源被釋放後,依次喚醒隊列中從前到後的所有節點,使他們對應的線程恢復執行,直到隊列爲空.
Condition隊列的作用是維護一個等待signal信號的隊列,兩個隊列的作用是不同,事實上,每個線程也僅僅會同時存在以上兩個隊列中的一個,流程是這樣的:
用上面的Demo的兩個線程來描述
1、首先,線程1調用lock.lock()時,由於此時鎖並沒有被其它線程佔用,因此線程1直接獲得鎖並不會進入AQS同步隊列中進行等待。
2、在線程1執行期間,線程2調用lock.lock()時由於鎖已經被線程1佔用,因此,線程2進入AQS同步隊列中進行等待。
3、在線程1中執行condition.await()方法後,線程1釋放鎖並進入條件隊列Condition中等待signal信號的到來。
4、線程2,因爲線程1釋放鎖的關係,會喚醒AQS隊列中的頭結點,所以線程2會獲取到鎖。
5、線程2調用signal方法,這個時候Condition的等待隊列中只有線程1一個節點,於是它被取出來,並被加入到AQS的等待隊列中。注意,這個時候,線程1 並沒有被喚醒。只是加入到了AQS等待隊列中去了
6、待線程2執行完成之後並調用lock.unlock()釋放鎖之後,會喚醒此時在AQS隊列中的頭結點.所以線程1開始爭奪鎖(由於此時只有線程1在AQS隊列中,因此沒人與其爭奪),如果獲得鎖繼續執行。
7、直到線程1釋放鎖整個過程執行完畢。
可以看到,整個協作過程是靠結點在AQS的等待隊列和Condition的等待隊列中來回移動實現的,Condition作爲一個條件類,自己維護了一個等待信號的隊列,並在適時的時候將結點加入到AQS的等待隊列中來實現的喚醒操作。
下面分析一下signal()方法
public final void signal() {
//檢測當前線程是否爲擁有鎖的獨佔線程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
/*
firstWaiter爲condition自己維護的一個鏈表的頭結點,
取出第一個節點後開始喚醒操作
*/
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//修改頭結點,完成舊頭結點的移出工作
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
doSignal()方法幹了兩件事:1、修改條件隊列中的頭結點,1、完成舊的頭結點的移出工作,即從Condition隊列中移出到AQS同步隊列中去。
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
//如果不能改變等待狀態,那麼當前節點被取消,return false
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
//將節點加入到syn隊列中去,返回的是syn隊列中node節點前面的一個節點
Node p = enq(node);
int ws = p.waitStatus;
//將節點加入到syn隊列中去,返回的是syn隊列中node節點前面的一個節點
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
可以看到,正常情況 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 這個判斷是不會爲true的,所以,不會在這個時候喚醒該線程.
只有到發送signal信號的線程調用reentrantLock.unlock()後,它已經被加到AQS的等待隊列中,纔可能會被喚醒.
ReentrantLock
ReentrantLock可重入鎖,與synchronized類似,但更方便靈活,可作爲替代使用:
1.支持公平/非公平鎖
2.支持響應超時,響應中斷
3.支持condition
ReentrantLock實現了Lock接口,內部繼承AQS實現這些功能,使用AQS的state來表示鎖的重入次數,lock接口如下
看下如何實現AQS的
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
//非公平鎖tryAcquire
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
//判斷state是否被佔用
if (c == 0) {//未被佔用則cas佔用,並設置當前線程爲佔用線程
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果被佔用,則判斷是否是當前線程佔用的(鎖可重入),如果是就把state+1
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) {//狀態-1後如果資源未被佔用,則沒有佔用線程
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
//當前線程是否是佔用線程
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
//獲取Codition,conditionObject維護一個條件隊列,見上篇
final ConditionObject newCondition() {
return new ConditionObject();
}
//獲取佔用線程
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
ReentrantLock的實現又分爲兩種,公平鎖、非公平鎖,咱們直接看非公平鎖(公平鎖前面分析AQS的時候分析過了,http://blog.csdn.net/kevindai007/article/details/70314814):
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);
}
}
ReentrantLock釋放鎖的過程在AQS的分析中都已經分析過了,在此不做重複分析.
ReetrantLock的公平和非公平的區分就是在Acquire的時候,非公平會先直接嘗試cas修改,不成功再去排隊;而公平鎖就是老老實實請求排隊操作。