JAVA中线程间通信的小故事

683 阅读6分钟

从掘金的大佬中偷学到一个技能,为了提升知识提炼与字面表达能力,斟酌贴代码的篇幅,尽量用文字表达清楚技术知识的本质。(简单点就是“多说人话”)

正文开始!

前情提要

关于“线程间通信”的这个叫法,没查到比较官方的定义,也许它是一个通俗词吧。下面是基于笔者个人理解所总结出的定义,重在严谨。

不同线程之间通过资源状态同步相互影响彼此的执行逻辑。

线程间的基本通信可以划分为启动与结束,线程等待与唤醒线程。在JAVA中他们都对应了固定的API与固定用法,是还存在其他的通信方式,但本文不做展开。

一、线程停止的性格差异

张三与小明的故事

1. thread.stop() 愚蠢且粗鲁的张三

立即强制停止某个线程的执行,中断代码执行指令,并退出线程。被停止的线程无法安全的进行善后处理。

代入角色举个栗子,愚蠢的张三安排他儿子小明烧开水。小明很聪明,已经牢记了烧水的步骤,拿锅接水,开火,水沸关火。

小明很听话,便进入厨房开始了忙碌。

半分钟后愚蠢张三的电话突然响了 ,有关部门通知马上会停止燃气供应,张三意识到小明不能再烧水了,决定停止小明的工作。

此时小明还在接水,但愚蠢的张三假装没看见,他一把将小明拉出了厨房。小明内心非常懵逼,但是他有苦说不出。

他们离开后,厨房的水还在哗哗的流,最后淹了厨房...

总结:完全不需要考虑善后的线程才能用stop()

2. thread.interrupt() 温柔的张三

通知某个线程中断当前在执行的任务,被中断的线程可以先进内部善后处理,再退出线程,或者不退出。

代入角色,还是上面的栗子。这不过这次张三并没后直接抱走小明,而是大声告诉小明,该离开厨房了。

小明此时有两种选择,第一中是丢下手上的事情,马上走出厨房,让水继续哗哗的流。第二种是关闭水龙头再走出厨房。

如果你是小明,你准备怎么做?

总结:中断线程用thread.interrupt()就对了,最起码温柔

3.Thread.interrupted() 可怜的小明,正确答案只能获取一次

每次在被中断后的第一次调用时返回true,之后在没有被在此中断前都一直返回false

代入角色,还是上面的栗子。小明知道张三有可能会通知他出现了例外情况,所以小明在每一个关键步骤前检查是否需要停止,如果发现被叫停就马上进行善后工作,离开厨房。因为他知道他基本只有一次机会。

总结:用于简单任务的中断判断,如果无法衡量是否简单,那就没必要用,除非你对中断次数是非常敏感的。

4.isInterrupted() 快乐的小明,获取正确答案不限次数

只要被中断过一次,之后获取到的状态都是true

小明的快乐你懂了吗

总结:小明的快乐你懂了吗

5.Thread.sleep(x)小明在厨房睡着了

当前线程进入挂起状态,挂起的过程中可能会被中断,被中断时则会被catch (InterruptedException e)捕获,可以进行善后处理,选择是否退出。

代入角色,没错小明真睡着了!如果温柔的张三大声告诉小明离开厨房,小明被惊醒后要是不犯迷糊就会有序的停止当且阶段的工作,比如关闭水龙头,然后离开厨房。

要是小明犯迷糊呢?小明一般不会犯迷糊

因为他知道

犯迷糊的小明会被张三暴揍!

总结:Thread.sleep(x)后需要捕获的异常catch (InterruptedException e),理解为例外更好些,因为它并不代表程序错误

二、等待的细节与唤醒的差别

小明与小芳的故事

1.wait() 小明的素质

当需要访问的资源不满足条件时,选择进入等待区。直到被唤醒后重新竞争锁,获取锁后接着之前的逻辑继续执行

小明和小芳一起看电视,小明先抢到了遥控器,他想看足球比赛,切到了足球频道,球员A准备射门,但是小明点的啤酒还没到,小明看比赛必须得有啤酒。

如果小明没礼貌,那么他就暂停电视,把遥控器坐在屁股下面,一直盯着电视,直到啤酒来了,小明恢复电视,继续看。

如果小明有礼貌,那么他就先让出了遥控器,小芳拿到遥控器开心的放起了甄嬛传。 小明呢则开始发呆(细节1),直到(细节2)有人告诉他啤酒来了,他便重新(细节3)去抢遥控器,抢到后遥控器后起到足球频道,电视机画面直接从球员A准备射门处(细节4)开始播放。

如果小明发呆的时候出现了意外怎么办呢?不用担心这会立即叫醒小明,他可以自主选择下一步怎么办。

这就是为什么wait()时也需要catch (InterruptedException e)

总结:用wait()让出锁和资源,减少兄弟线程的等待时间

2.notify() 幸运女神

由当前作为锁的对象随机从与当前锁相关且进入wait()的线程中唤醒一个,被唤醒的线程重新进行锁的竞争

从上帝视角看,当资源只能满足一个线程使用时,使用notify(),能节约不必要的额外开销。

而被选中的那个线程就是唯一的幸运儿~

3.notifyAll() 阳光普照

由当前作为锁的对象唤醒所有与当前锁相关且进入wait()的线程,被唤醒的线程重新进行锁的竞争

如果没有特殊考虑,为了世界和平,通常你应当唤醒所有进入等待的线程。

三、join 快来绑一绑timing

将多个并行线程任务,连成一个串行的线程任务,带头线程不管成功还是失败,跟随线程都会立即执行

再举个栗子吧,张三安排小芳做饭,并让小明负责打酱油。

接下来的情况就会变得非常有趣。

小芳炒完菜要出锅的时候需要酱油,但是此时小明还没有买回酱油。小芳便使用join大法将自己绑定到了小明买回酱油这件任务的结束timing上。

结果呢?如果小明顺利买回了酱油,小芳使用酱油提鲜后装盘出锅。

如果小明路上摔跤了,导致提前退出了任务。小芳则使用空酱油后装盘出锅

这不怪小芳,她哪知道小明没有带回酱油呢。

总结: join()之后应该在此判断条件是否满足,避免拿到NPE

四、yield

稍微让出一点时间片给同级别线程,又立即恢复自己的执行。

像是快速wait()(不用别人叫的那种),再快速自动恢复

缺少科学分析验证,不敢多说~


END