和 Object 的 wait()/notify()
对比
Object.wait/notify有的, Condition 都有
wait/notify与synchronized配合使用 , 显式条件与显式锁配合使用。
aCondition.await()
--------aObject.wait()
aCondition.signal()
--------aObject.notify()
Object.wait/notify 的缺陷
- 欺骗性唤醒(Java缺陷, 没通知就唤醒)
- wait(限时) 是void 不会返回是等到了还是超时了
- 只能把等待的都唤醒, 不能分组唤醒
所以 一律用Condition 的 await/signal
这对,上下文切换比较少
基本使用:
如下 await()
signal()
必须在lock.lock()
后使用
public class WaitThread extends Thread {
private volatile boolean fire = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();//加锁---------------------------------------------------------------------------------
try {
while (!fire) {//1.保护条件
condition.await();//2.等待
}
//3.目标动作
} finally {
lock.unlock();//解锁-------------------------------------------------------------------------
}
System.out.println("fired");
} catch (InterruptedException e) {
Thread.interrupted();
}
}
注意:
-
while (!fire)
保护条件必须这样循环 而不是if
, 再检查一遍 - 1.保护条件 2.等待 3.目标动作 --------三者必须在一个临界区
通知唤醒↓:
public void fire() {
lock.lock();//---------------------------------------------------------------------------------------
try {
//紧挨着lock.unlock();最后才写唤起其他线程
this.fire = true;//1.条件满足
condition.signal();//2.叫醒
} finally {
lock.unlock();//--------------------------------------------------------------------------------
}
}
}
注意:
- 通知 必须在临界区之内, 只有自己得到锁了 才能通知其他线程
- 通知尽量在临界区最后写: 因为通知后 本线程结束了, 锁释放了, 被唤醒的才能继续
public static void main(String[] args) throws InterruptedException {
WaitThread waitThread = new WaitThread();
waitThread.start();
Thread.sleep(1000);
waitThread.fire();
}
超时控制
// 计算等待的最后期限
final Date deadline = new Date(System.currentTimeMillis() + timeOut);
// 是否继续等待
boolean continueToWait = true;
lock.lock();
try {
while (!ready) {
// 等待未超时,继续等待
if (!continueToWait) {
// 等待超时退出
return;
}
continueToWait = condition.awaitUntil(deadline);
}// while循环结束
// 执行目标动作
} finally {
lock.unlock();
}
}
-
continueToWait = condition.awaitUntil(deadline);
会返回是不是超时了 - 有2个出口, 一个是
while(条件满足)
, 一个是if (!continueToWait)
超时了也要结束
分组唤醒
比synchronized好的地方还有,可以分组唤醒
比如生产者消费者,用synchronized的话, 有生产了一个 只能把 [生产者] 和 [消费者] 都唤醒,而不是只把 [消费者] 唤醒
避免了不必要的唤醒。Java并发包中的类ArrayBlockingQueue就采用了类似的方式实现。
原理
ReentrantLock
里面有等待队列
也有条件队列
signal
满足条件把条件队列移到等待队列最后
还是原来先在等的线程优先
↓ do while 循环把条件队列里面按顺序转移到阻塞队列
↓把节点连入双向链表 并且前一个节点要标记 有后继要唤醒