线程 进程 面试(面试官让我讲下线程的TIMED_WAITING状态,我又笑了)

来源:https://dwz.cn/4fMSzL4d

面试官Q:你讲下线程状态中的WAITING状态,什么时候会处于这个状态?什么时候离开这个状态?

小菜J 会心一笑。

面试官Q:那你继续讲下TIMED_WAITING状态

小菜J,又笑了。

一个正在限时等待另一个线程执行一个动作的线程处于这一状态。

A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.

更详细的定义还是看 javadoc:

带指定的等待时间的等待线程所处的状态。一大流量卡个线程处于这一状态是因为用一个指定的正的等待时间(为参数)调用了以下方法中的其一:

Thread.sleep带时限(timeout)的 Object.wait带时限(timeout)的 Thread.joinLockSupport.parkNanosLockSupport.parkUntil

对应的英文原文如下:

Thread state for a waiting thread with a specified waiting time. A thread is in the timed waiting state due to calling one of the following metho大流量卡ds with a specified positive waiting time:

Thread.sleep

Object.wait with timeout

Thread.join with timeout

LockSupport.parkNanos

LockSupport.parkUntil

不难看出,TIMED_WAITING 与 WAITING 间的联系还是很紧密的,主要差异在时限(timeout)参数上。

另外则是 sleep 这一点上的不同。

timed_waiting 的场景

实际上,在上一篇章中谈到的没有参数的 wait() 等价于 wait(0),而 wait(0) 它不是等0毫秒,恰恰相反大流量卡,它的意思是永久的等下去,到天荒地老,除非收到通知。

具体可见 java 的源代码及相应 javadoc,注意:同时又还存在一种特殊的情况,所谓的“spurious wakeup”(虚假唤醒),我们在下面再讨论。

即是把自己再次活动的命运完全交给了别人(通知者),那么这样会存在什么问题呢?

在这里,我们还是继续上一篇章中的谈到的车厢场景,如不清楚的参见 Java 线程状态之 WAITING。

设想一种情况,乘务员线程增加了厕纸,正当它准备执行 notify 时,这个线程因某种原因被杀死了(持有的锁也随之释放)。这种情况下,条件已经满足了,但等待的线程却没有收到通知,还在傻乎乎地等待。

简而言之,就是存在大流量卡通知失效的情况。这时,如果有个心机婊线程,她考虑得比较周全,她不是调用 wait(),而是调用 wait(1000),如果把进入 wait set 比喻成在里面睡觉等待。那么 wait(1000)相当于自带设有倒计时 1000 毫秒的闹钟,换言之,她在同时等待两个通知,并取决于哪个先到:

如果在1000毫秒内,她就收到了乘务员线程的通知从而唤醒,闹钟也随之失效;反之,超过1000毫秒,还没收到通知,则闹钟响起,此时她则被闹钟唤醒。

这种情况类似于双保险。下面是一个动态的 gif 示意图(空的电池代表条件不满足,粉色的乘务员线程负责增加纸张,带有闹钟的乘客线程代表限时等待):

这样,在通知失效的情况下大流量卡,她还是有机会自我唤醒的,进而完成尿尿动作。

可见,一个线程,她带不带表(闹钟),差别还是有的。其它死心眼的线程则等呀等,等到下面都湿了却依旧可能等不来通知。用本山大叔的话来说:那憋得是相当难受。

以下代码模拟了上述情形,这次,没有让乘务员线程执行通知动作,但限时等待的线程2还是自我唤醒了:

虚假唤醒(spurious wakeup)

虽然,前面说到没有参数的 wait() 等价于 wait(0),意思是永久的等下去直到被通知到。但事实上存在所谓的 “spurious wakeup”,也即是“虚假唤醒”的情况,具体可见 Object.wait(long timeout) 中的 javadoc 说明:

A大流量卡 thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the conditi大流量卡on is not satisfied.

一个线程也能在没有被通知、中断或超时的情况下唤醒,也即所谓的“虚假唤醒”,虽然这点在实践中很少发生,应用应该检测导致线程唤醒的条件,并在条件不满足的情况下继续等待,以此来防止这一点。

换言之,wait 应该总是在循环中调用(waits should always occur in loops),javadoc 中给出了样板代码:

简单讲,要避免使用 if 的方式来判断条件,否则一旦线程恢复,就继续往下执行,不会再次检测条件。由于可能存在的“虚假唤醒”,并不意味着条件是满足的,这点甚至对简单的“二人转”的两个线程的 wait/notify 情况也需要注意。

另外大流量卡,如果对于更多线程的情况,比如“生产者和消费者”问题,一个生产者,两个消费者,更加不能简单用 if 判断。因为可能用的是 notifyAll,两个消费者同时起来,其中一个先抢到了锁,进行了消费,等另一个也抢到锁时,可能条件又不满足了,所以还是要继续判断,不能简单认为被唤醒了就是条件满足了。

关于此话题的更多信息,可参考:

Doug Lea 的 《Concurrent Programming in Java (Second Edition)》3.2.3 节。Joshua Bloch 的 《Effective Java Programming Language Guide》,“Prefer concu大流量卡rrency utilities to wait and notify”章节。

sleep 时的线程状态

进入 TIMED_WAITING 状态的另一种常见情形是调用的 sleep 方法,单独的线程也可以调用,不一定非要有协作关系,当然,依旧可以将它视作为一种特殊的 wait/notify 情形。

这种情况下就是完全靠“自带闹钟”来通知了。

另:sleep(0) 跟 wait(0) 是不一样的,sleep 不存在无限等待的情况,sleep(0) 相当于几乎不等待。

需要注意,sleep 方法没有任何同步语义。通常,我们会说,sleep 方法不会释放锁。

javadoc中的确切说法是:The thread 大流量卡does not lose ownership of any monitors.(线程不会失去任何 monitor 的所有权)

而较为夸张的说法则是说 sleep 时会抱住锁不放,这种说法不能说说错了,但不是很恰当。

打个不太确切的比方,就好比你指着一个大老爷们说:“他下个月不会来大姨妈”,那么,我们能说你说错了吗?但是,显得很怪异。

就锁这个问题而言,确切的讲法是 sleep 是跟锁无关的。

JLS 中的说法是“It is important to note that neither Thread.sleep nor Thread.yield have any

synchronization sema大流量卡ntics”。(sleep 和 yield 均无任何同步语义),另一个影响是,在它们调用的前后都无需关心寄存器缓存与内存数据的一致性(no flush or reload)

见《The Java Language Specification Java SE 7 Edition》17.3 Sleep and Yield

所以,如果线程调用 sleep 时是带了锁,sleep 期间则锁还为线程锁拥有。

比如在同步块中调用 sleep(需要特别注意,或许你需要的是 wait 的方法!)

反之,如果线程调用 sleep 时没有带锁(这也是可以的,这点与 wait 不同,不是非得要在同步块中调用),那么自然也不大流量卡会在sleep 期间“抱住锁不放”。

压根就没有锁,你让它抱啥呢?而 sleep 君则完全是一脸懵逼:“锁?啥是锁?我没听过这玩意!”

带 timeout 的 join 的情景与 wait(timeout) 原理类似,这里不再展开叙述。

LockSupport.parkNanos 和 parkUnitl 也交由读者自行分析。

总结

最后,跟传统进(线)程状态划分的一个最终对比:

我目前是在职Java开发,如果你现在正在了解Java技术,想要学好Java,渴望成为一名Java开发工程师,在入门学习Java的过程当中缺乏基础的入门视频教程,你可以关注并私信我:01。我这里有一套最新的Java基础JavaSE的大流量卡精讲视频教程,这套视频教程是我在年初的时候,根据市场技术栈需求录制的,非常的系统完整。


友情提醒: 请添加客服微信进行免费领取流量卡!
QQ交流群:226333560 站长微信:qgzmt2

原创文章,作者:sunyaqun,如若转载,请注明出处:https://www.dallk.cn/61101.html

(0)
sunyaqunsunyaqun
上一篇 2024年8月18日
下一篇 2024年8月18日

相关推荐

发表回复

登录后才能评论