condition的作用
condition的使用場景其實很多,涉及到條件判斷的并發(fā)場景都可以用到,比如:
- 阻塞隊列的ArrayBlockingQueue中做隊列滿和空的條件判斷
- CyclicBarrier中做阻塞與喚醒所有線程的判斷
- DelayQueue中的阻塞獲取隊列數據的判斷
- 線程池ThreadPoolExecutor中awaitTermination方法的條件判斷
condition怎么用呢?
在使用synchronized時我們可以使用wait()、notify()、notifyAll()方法來調度線程,而condition提供了類似的方法:wait(),signal(),signalAll的功能,并且能夠更加精細的控制等待的范圍,像上面所說,jdk中使用了很多ReentrantLock和condition的配合來實現線程調度
我們看一個conditon最常見的使用方式:生產消費者的模型:
public class ConditionTest {
LinkedList<String> lists = new LinkedList<>();
Lock lock = new ReentrantLock();
//集合是否滿的條件判斷
Condition fullCondition = lock.newCondition();
//集合是否空的條件判斷
Condition emptyCondition = lock.newCondition();
//生產者
private void product(){
lock.lock();
try {
//假如集合大小為10
while (lists.size() == 10){
System.out.println("list is full");
fullCondition.await();
}
//生產一個5位的隨機字符串
String randomString = getRandomString(5);
lists.add(randomString);
System.out.println(String.format("product %s size %d %s",randomString,lists.size(),Thread.currentThread().getName()));
//通知消費者可以消費了
emptyCondition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
//消費者
private String consume(){
lock.lock();
try{
while (lists.size() == 0){
System.out.println("list is empty");
emptyCondition.await();
}
String first = lists.removeFirst();
//通知生產者可以生產了
fullCondition.signalAll();
return first;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
return null;
}
/**
* 生成隨機字符串
* @param length
* @return
*/
public static String getRandomString(int length){
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int i=0;i<length;i++){
int number=random.nextInt(62);
sb.append(str.charAt(number));
}
return sb.toString();
}
public static void main(String[] args) {
ConditionTest test = new ConditionTest();
ExecutorService executorService = Executors.newCachedThreadPool();
//線程個數控制消費的快還是生產的快
for(int i = 0;i<2;i++){
executorService.submit(()->{
System.out.println(Thread.currentThread().getName());
while (true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.product();
}
});
}
for(int k = 0;k<1;k++){
executorService.submit(()->{
System.out.println("cousumestart");
while (true) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
String consume = test.consume();
System.out.println("consume " + consume+ " "+Thread.currentThread().getName() );
}
});
}
//等待輸入,阻塞主線程不退出
try {
new BufferedReader(new InputStreamReader(System.in)).readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
//部分輸出日志
product qeV0r size 7 pool-1-thread-1
product xEUkA size 8 pool-1-thread-2
consume P5Je1 pool-1-thread-3
product rQS1D size 8 pool-1-thread-1
product QcEtf size 9 pool-1-thread-2
consume 2q7Fc pool-1-thread-3
product Z5rBg size 9 pool-1-thread-1
consume UBxBD pool-1-thread-3
product Tr5q2 size 9 pool-1-thread-2
product HXBdE size 10 pool-1-thread-1
list is full
consume aYDNR pool-1-thread-3
product ukjnk size 10 pool-1-thread-2
list is full
consume LBEdA pool-1-thread-3
product iK28H size 10 pool-1-thread-2
list is full
list is full
可以看到生產者線程有2個,消費者線程有1個,生產和消費的速度相同,用Thread.sleep控制,
生產速度大于消費速度,最后集合元素到10個的時候生產者調用fullCondition.await();
阻塞,只有消費者消費后通過fullCondition.signalAll();
通知生產者繼續(xù)生產
同理添加消費者線程數,使消費的速度快與生產,則集合為空時會調用emptyCondition.await();
阻塞,生產者生產后回調用emptyCondition.signalAll();
通知消費者繼續(xù)生產
相較于對象的wait()、notifyAll()方法不同的條件分開判斷,顆粒度更小一些,喚醒的線程范圍更精準
再看一下ArrayBlockingQueue的一個例子,在一段時間內阻塞獲取隊列數據,取不到則返回空:
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
long nanos = unit.toNanos(timeout);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == 0) {
if (nanos <= 0)
return null;
//notEmpty 是lock new出來的一個condition
nanos = notEmpty.awaitNanos(nanos);
}
return dequeue();
} finally {
lock.unlock();
}
}
condition的使用場景還多,下面我們就一起看看condition的實現原理吧,首先condition需要在AbstractQueuedSynchronizer實現類的
condition原理解析
我們知道AQS中維護了一個隊列來控制線程的執(zhí)行,condition中使用了另一個等待隊列來實現條件的判斷,condition必須在aqs的acquire獲取鎖后使用,調用condition.await()方法將添加一個node到條件隊列中,在調用signal()或signalAll()后將此節(jié)點移出condition的等待隊列放到鎖的等待隊列中去競爭鎖,取到鎖后繼續(xù)執(zhí)行后續(xù)邏輯。
condition有以下幾個方法
//將等待時間最長的線程從condition等待隊列放到鎖的等待隊列中
public final void signal()
//將所有等待線程從condition等待隊列放到鎖的等待隊列中
public final void signalAll()
//condition的等待方法
public final void await() throws InterruptedException
//不可中斷的wait
public final void awaitUninterruptibly()
//幾個有時間參數的wait方法
public final long awaitNanos(long nanosTimeout)
throws InterruptedException
public final boolean awaitUntil(Date deadline)
throws InterruptedException
public final boolean await(long time, TimeUnit unit)
throws InterruptedException
先看一下最主要的await方法
AbstractQueuedSynchronizer.ConditionObject#await()
public final void await() throws InterruptedException {
//如果當前線程被中斷了拋出InterruptedException
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();//(1)
int savedState = fullyRelease(node);//(2)
int interruptMode = 0;
while (!isOnSyncQueue(node)) {//(3)
//掛起線程
LockSupport.park(this);
//中斷情況的判斷
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//被喚醒后去搶鎖,搶到后繼續(xù)執(zhí)行
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//如果阻塞中發(fā)生了中斷,則拋出異常
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
(1)addConditionWaiter
在condition等待隊列尾部加入一個節(jié)點
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果最后一個節(jié)點不是condition狀態(tài)(被取消狀態(tài))被取消狀態(tài)是在fullyReleas方法中產生的
if (t != null && t.waitStatus != Node.CONDITION) {
//從頭節(jié)點開始將被取消或者超時的節(jié)點移出隊列
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//隊列為空的情況
if (t == null)
firstWaiter = node;
else
//插入尾節(jié)點
t.nextWaiter = node;
lastWaiter = node;
return node;
}
(2)fullyRelease
能調用wait方法說明已經獲取到鎖了,fullyRelease方法就是提前調用解鎖方法,將自己從lock的隊列中移出,并返回當前節(jié)點的狀態(tài)savedState,這里如果釋放失敗說明當前線程不在持有鎖,狀態(tài)錯誤,將節(jié)點設置成CANCELLED狀態(tài)
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;
}
}
release方法調用tryRelease釋放鎖并喚醒首節(jié)點,在ReentrantLock的實現中tryRelease會判斷當前線程是否獲取鎖,所以在lock方法范圍內使用condition會報IllegalMonitorStateException異常
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
(3)isOnSyncQueue
回到await方法,循環(huán)調用isOnSyncQueue判斷是否在鎖的等待隊列中(注意不是condition的等待隊列),不在鎖的等待隊列中則調用LockSupport.park(this)
掛起線程。
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}
awaitNanos方法
大致邏輯和await相同,就是多了一個時間的判斷
public final long awaitNanos(long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
final long deadline = System.nanoTime() + nanosTimeout;
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//如果時間小于0,直接從condition隊列
if (nanosTimeout <= 0L) {
transferAfterCancelledWait(node);
break;
}
//如果大于自旋的閾值則使用parkNanos設置線程掛起的時間,否則繼續(xù)自旋
if (nanosTimeout >= spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
nanosTimeout = deadline - System.nanoTime();
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
return deadline - System.nanoTime();
}
signal()方法
signal的作用是將condition隊列中等待時間最長的node轉移到鎖隊列末尾,去重新搶鎖
public final void signal() {
//有不同的實現,ReentrantLock中是判斷持有鎖的是否當前線程
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
doSignal
將condition中等待時間最長的節(jié)點調用transferForSignal方法放到鎖隊列中,循環(huán)調用是要尋找第一個不是cancelled狀態(tài)的節(jié)點
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
doSignalAll
doSignalAll是將所有等待隊列中的節(jié)點放到鎖隊列末尾
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
transferForSignal
final boolean transferForSignal(Node node) {
//cas設置節(jié)點為0狀態(tài),如果失敗說明節(jié)點已經被取消了
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).
*/
//添加到鎖隊列中
Node p = enq(node);
int ws = p.waitStatus;
//cancelled狀態(tài)或者設置SIGNAL狀態(tài)失敗則喚醒此線程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
condition中有很多線程與中斷的細節(jié)處理,有興趣的可以自己去看看源碼
總結一下:
- condition必須使用在lock中
- condition提供了類似object.wait和notify的通信機制,但支持多個條件隊列,使用上更靈活
- condition的原理流程如下
- 線程1獲取鎖
- 線程1調用condition.await()進入condition等待隊列并阻塞,釋放鎖給別的線程
- 線程2獲取鎖,調用condition.signal,將condition等待隊列中的線程1所在的node放在鎖的等待隊列中競爭鎖