wait、notify、notifyAll、sleep、yeild

一、wait、notify、notifyAll

每个Java对象,除了有一个与之相关的 monitor ,还有一个与之对应的 wait 集合,这个集合中装的便是在该对象上的等待线程。

在很多源码中,我们都看到不管是 wait 还是 notify 亦或是 notifyAll,其调用全是在该对象的synchronized同步语句块中。这是为什么?
1、首先,我们很多地方都能找到 wait/notify 使用的例子,可以总结出线程是在某种条件下才进行 wait,在符合另一个条件的情况下被另一线程 notify 唤醒,也就是涉及检测、wait/notify至少两步。
2、然后,wait 和 notify用来进行线程间通信使用,而不同的线程之间存在竞态,这便导致了如果不在同步块中操作可能出现的Lost wake up的问题。
3、最后,如果不加同步,可能出现 notify 所在线程检测到符合 notify 条件,发出 notify 通知,此时 wait 所在线程并未在等待 notify ,而只是检测到符合条件要进行wait,那么该通知就丢失,然后 wait 所在线程才往下执行进入等待,然后就一直等下去。

关于wait

如果在未拥有某个对象的锁(表示线程拥有对象的 monitor )之前,在该对象上直接调用 wait 方法,抛 IllegalMonitorStateException 异常。
如果 wait 带参数,nanosecs 必须在 0 - 999999之间,millisecs 不能为负,否则报 IllegalArgumentException 异常。

正常情况下,在wait被调用之后,一般会发生如下次序的事件:

在某个对象m上调用 wait 之后,线程不再进一步执行任何指令,停止运行。线程被加入到等待集合中。

然后在对象m上执行unlock动作,这使得进入wait方法之前在对象m上执行的lock动作被解除。

注:下文所说的恢复运行,均指线程恢复到等待动作继续。需要重新 lock 对象的 monitor,才能从wait中跳出而继续执行。

一旦线程 T 被加入到等待集合中,那么什么时候恢复运行呢?对应以下几种情况:
1、在被加锁对象上执行 notify 调用,线程 T 被选中并从 wait 集合中移除,线程 T 恢复运行。
2、在被加锁对象上执行 notifyAll 调用,此时在对象的wait集合中的线程都被移除,均恢复运行。
3、在线程 T 上执行了中断操作,线程T从 wait 集合中移除,然后线程恢复到等待阶段继续运行,wait 方法在获得(成功lock)对象的 monitor 之后抛 InterruptedException 异常,线程 T 的中断状态被置为false。
4、如果设置了等待时间,那么在调用 wait 之后过了等待时间,由内部操作将线程 T 从等待队列移除,线程恢复运行。

中断和 notify 发生时的次序处理:

扫描二维码关注公众号,回复: 10765063 查看本文章

线程 T 在对象 m 等待队列中,同时生了线程T的中断操作和对象 m 的 notify 操作,两个事件必须确定一个次序

  • 如果中断被认为先发生,那么线程 T 通过抛 InterruptedException 从 wait 方法返回,等待集合中的其它线程接收 notification,也就是说notification不能丢失
  • 如果notify被认为先发生,那么线程 T 便从wait方法正常返回,并且线程 T 的中断挂起。

线程从wait恢复以后,对在进入wait方法后解除lock的对象重新执行lock动作,获取锁之后才继续往下执行。

对于 notify

在某个对象上调用 notify,会使该对象的等待队列当中的一个线程被移除,但不能保证确切的哪一个线程会被移除。

需要注意的是,在线程从 wait 恢复时对对象 m 的加锁动作,直到对象m上的monitor全部被unlock之前都是不能成功的。

如果是在某个对象上调用 notifyAll,会使该对象的等待队列当中的全部线程都被移除然后恢复,但是一次仅仅只有一个线程能够获得从wait恢复期间需要的monitor。

二、sleep和yield

sleep是让线程进入睡眠状态,停止一段时间,执行者和针对的都是当前线程。执行sleep之后线程不会丧失任何monitor的拥有权。另一方面,线程能否继续运行,也取决于调度器以及当前可用的核。

Thread.sleep 和 Thread.yield 均不带任何同步语义,也就是说在调用 Thread.sleep 和 Thread.yield 之前,编译器不需要将写入到寄存器的值刷到共享内存,也不需要在调用Thread.sleep和Thread.yield之后,从共享内存重新加载缓存在寄存器中的值。

yield和sleep有些相似,也是让线程停止运行,受让其所占用的CPU,不过yield只是让线程进入到就绪状态,随时可被重新调度运行,取决于调度器和当前可用的核。

三、简要对比

wait、notify、notifyAll:用于线程间通信使用,只能在同步块中使用,使用时放弃monitor的拥有权,是Object方法。
sleep、yield:仅用于线程的状态控制,可以在任何地方使用,不丧失任何monitor的拥有权,是静态方法。

发布了95 篇原创文章 · 获赞 5 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43878293/article/details/104690105