wait
notify
还有个notifyAll
都是线程通信的常用手段。本文会简要介绍其底层实现原理,并和Condition
的await
和signal
方法作对比。
有一个先导概念就是对象锁和类锁,他们其实都是对象监视器Object Monitor
,只不过类锁是类对象的监视器,可以看另一篇文章:
Java-并发-锁-synchronized之对象锁和类锁
在调用wait和notify之前必须持有对象锁,那么就必须了解synchronized
,可以参阅文章:
Java-并发-锁-synchronized
更多关于Java锁的信息,可参考文章:Java-并发-关于锁的一切
interrupted
方法,会抛出InterruptedException
,且中断标记会被自动清理。先看没有参数的版本:
/**
* 让当前线程等待到指定Object,直到其他线程调用该对象的notify或notifyAll方法唤醒
* 该方法等价于调用wait(0)
*
* 注意 调用该方法前提是拥有该对象的对象锁。否则会报错抛出IllegalMonitorStateException
*
* 当拥有对象锁并调用wait方法时,会释放对象锁,
* 然后等待,直到其他线程调用该对象的notify或notifyAll方法唤醒那些wait在该对象锁上的线程。
* 唤醒之后,该线程会尝试去获取对象锁,拿不到就等到直到拿到
* 拿到对象锁后继续执行程序。
*
* 在单参数的wait方法版本中,中断和意料之外的唤醒是可能的所以应该这么做:
* synchronized (obj) {
* while (condition does not hold)
* obj.wait();
* ... // Perform action appropriate to condition
* }
*
* @throws IllegalMonitorStateException 调用线程未持有该对象的对象锁.
* @throws InterruptedException
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final void wait() throws InterruptedException {
wait(0);
}
再看看带1个参数版本的wait方法:
/**
* 让当前线程等待到指定Object,直到其他线程调用该对象的notify或notifyAll方法唤醒
* 或是指定wait超时时间耗尽
*
* 注意 调用该方法前提是拥有该对象的对象锁。否则会报错抛出IllegalMonitorStateException
*
* 该方法的原理:
* 1.调用wait方法的线程将自己加入该对象的等待结合中
* 2.然后放弃所有和该对象相关的同步锁声明
* 3.该调用线程随后就不能被调度器调度执行了,进入休眠状态直到以下情况发生:
* 1.其他线程对目标对象调用notify方法,刚好选中该线程被唤醒
* 2.其他线程对目标对象调用notifyAll方法唤醒所有线程
* 3.其他线程对该线程调用interrupt方法发起中断
* 4.指定的超时时间耗尽,前提是超时时间不是0
* 4.该线程被唤醒后,从等待该对象的集合中移除,又可以被调度执行了
* 5.此时会跟其他线程一起竞争该对象的同步锁
* 6.一旦该线程拿到对象同步锁,所有在wait方法执行前的同步说明都重新起效
* 7.然后该线程就从wait方法中返回了,该过程结束
*
* 除了上面提到的几种唤醒场景,还有一种极少发生的情况会唤醒线程,称为`伪唤醒`
* 为了预防,所以应该这么做:
* synchronized (obj) {
* while (condition does not hold)
* obj.wait();
* ... // Perform action appropriate to condition
* }
*
* If the current thread is {@linkplain java.lang.Thread#interrupt()
* interrupted} by any thread before or while it is waiting, then an
* {@code InterruptedException} is thrown. This exception is not
* thrown until the lock status of this object has been restored as
* described above.
* 这段话没看的太明白?
*
* 注意这个wait方法只会让该线程释放当前Object的对象锁,而不会放弃拥有的其他对象锁!
*
*
* @param timeout the maximum time to wait in milliseconds.
* @throws IllegalArgumentException if the value of timeout is
* negative.
* @throws IllegalMonitorStateException if the current thread is not
* the owner of the object's monitor.
* @throws InterruptedException
* @see java.lang.Object#notify()
* @see java.lang.Object#notifyAll()
*/
public final native void wait(long timeout) throws InterruptedException;
可以先点击这里回顾下关于ObjectWatier
的知识。
然后我们继续分析底层源码。
wait/notify/notifyAll
代码主要在jdk8/hotspot/src/share/vm/runtime/synchronizer.cpp
里。
下面看看wait方法底层实现,摘录部分核心代码如下:
// 注意,必须使用重量级monitor来处理wait方法
// 第一个参数是句柄指向了我们wait的目标Object
// 第二个参数是wait的毫秒数
// 第三个是调用wait的线程
void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
if (UseBiasedLocking) {
// 如果开启了偏向锁
// 尝试获取该偏向锁,注意偏向锁是可重入的
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
if (millis < 0) {
// wait超时时间不可小于0
TEVENT (wait - throw IAX) ;
THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
// 膨胀为重量级锁,得到该ObjectMonitor
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
// 调用该monitor的wait方法
monitor->wait(millis, true, THREAD);
}
ObjectMonitor
相关代码在
/Users/chengc/cc/work/projects/jdk8/hotspot/src/share/vm/runtime/objectMonitor.hpp
/Users/chengc/cc/work/projects/jdk8/hotspot/src/share/vm/runtime/objectMonitor.cpp
下面看看wait方法,摘录部分核心代码如下:
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS){
// 以本身的ObjectMonitor创建一个ObjectWaiter
ObjectWaiter node(Self);
// 将该ObjectWaiter状态设为TS_WAIT
node.TState = ObjectWaiter::TS_WAIT ;
// 在这个AddWaiter时出现线程竞争的情况很少,所以采用了轻量级的自旋锁
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
// 添加该ObjectWaiter node到双向链表WaitSet中
AddWaiter (&node) ;
Thread::SpinRelease (&_WaitSetLock) ;
// 累加waiter
_waiters++;
// 释放ObjectMonitor
// 当调用返回后,其他线程就可以使用enter()方法竞争ObjectMonitor了
exit (true, Self) ;
// 线程现在可以用park()方法阻塞了
// 代码注释说以后要 change the following logic to a loop of the form
// while (!timeout && !interrupted && _notified == 0) park()
}
使用了队列的尾插法,到WaitSet
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
// 插入双向链表组成的队列的尾部
if (_WaitSet == NULL) {
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
该方法在jdk8/hotspot/src/os/linux/vm/os_linux.cpp
,主要是通过以下代码实现阻塞:
pthread_mutex_lock(_mutex)
while (_Event < 0) {
status = pthread_cond_wait(_cond, _mutex);
// for some reason, under 2.7 lwp_cond_wait() may return ETIME ...
// Treat this the same as if the wait was interrupted
if (status == ETIME) { status = EINTR; }
assert_status(status == 0 || status == EINTR, status, "cond_wait");
}
pthread_mutex_unlock(_mutex);
notify
的线程拥有,直到退出synchronized
块。IllegalMonitorStateException
。public final native void notify();
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
// 也是先用偏向锁
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
// 获取对象头的MarkWord
markOop mark = obj->mark();
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
// 如果拥有的是轻量级锁就直接返回了
return;
}
// 否则膨胀为重量级锁,调用得到的ObjectMonitor的notify方法
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
摘录部分核心代码如下:
void ObjectMonitor::notify(TRAPS) {
// 检查当前线程是否拥有该ObjectMonitor
CHECK_OWNER();
ObjectWaiter* iterator;
if (_WaitSet == NULL) {
// 如果WaitSet为空就返回了
TEVENT (Empty-NotifyAll) ;
return ;
}
// 自旋锁方式获取_WaitSetLock
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
// 获取WaitSet首节点,并从WaitSet移除该节点
ObjectWaiter * iterator = DequeueWaiter() ;
// 在此之后根据Knob_MoveNotifyee不同,对该节点做不同处理,如加入EntryList等
// 也就是说让该线程能重新竞争ObjectMonitor
// 最后释放_WaitSetLock
Thread::SpinRelease (&_WaitSetLock) ;
}
notifyAll
的线程拥有。IllegalMonitorStateException
。public final native void notifyAll();
跟notify差不多,其实就是循环的方式把WaitSet里的线程节点全部取出,放入EntryList。
经常面试会问这个问题,往往我们都是网上查资料死记硬背。现在我们都看完了源码(sleep源码点这里),可以得出以下结论
关于Condition介绍可以参考这篇文章:Java-并发-Condition
等待 | 唤醒 | 唤醒全部 | |
---|---|---|---|
Object | wait | notify | notifyAll |
Condition | await | signal | signalAll |
中断 | 超时精确 | Deadline | |
---|---|---|---|
wait | 可中断 | 可为纳秒 | 不支持 |
await | 支持可中断/不可中断 | 可为纳秒 | 支持 |
全部唤醒 | 唤醒顺序 | 执行前提 | 逻辑 | |
---|---|---|---|---|
notify | 支持,notifyAll | 随机(jdk写的,其实cpp源码是一个wait_queue,FIFO) | 拥有锁 | 从wait_list取出,放入entry_list,重新竞争锁 |
signal | 支持,signalAll | 顺序唤醒 | 拥有锁 | 从condition_queue取出,放入wait_queue,重新竞争锁 |
active
状态。调用wait方法后,线程对象被放入wait_queue。而notify会按FIFO方法从wait_queue中取得一个对象并放回entry_list,这样该线程可以重新竞争synchronized同步锁了。private static ReentrantLock lock = new ReentrantLock();
private static Condition notEmpty = lock.newCondition();
private static Condition notFull = lock.newCondition();
// 生产者
public void produce(E item) {
lock.lock();
try {
while(isFull()) {
// 数据满了,生产者就阻塞,等待消费者消费完后唤醒
notFull.await();
}
// ...生产数据代码
// 唤醒消费者线程,告知有数据了,可以消费
notEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
// 消费者
public E consume() {
lock.lock();
try {
while(isEmpty()) {
// 数据空了,消费者就阻塞,等待生产者生产数据后唤醒
notEmpty.await();
}
// ...消费数据代码
// 唤醒生产者者线程,告知有数据了,可以消费
notFull.signal();
return item;
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return null;
}
这样好处就很明显了。如果使用Object,那么唤醒的时候也许就唤醒了同类的角色线程。而使用condition可以在只有一个锁的情况下,实现我们想要的只唤醒对方角色线程的功能。
等待线程有可能意外被唤醒,需要用while循环继续判断是否被唤醒线程notify:
Object的阻塞和唤醒,是基于synchronized的。底层实现是在cpp级别。整个流程串起来如下:
synchronized
的线程对象会放入entry_list
active
状态wait
方法后,线程对象被放入wait_queue
notify
会按FIFO方法从wait_queue
中取得一个对象,并放回entry_listwait
的线程释放锁更多关于Java锁的信息,可参考文章:Java-并发-关于锁的一切
JVM源码分析之Object.wait/notify实现
图解Java多线程