注册

JAVA中线程间通信的小故事


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



正文开始!


前情提要


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


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


线程间的基本通信可以划分为启动与结束,线程等待与唤醒线程。在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准备射门处(细节3)开始播放。


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


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


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


2.notify() 幸运女神



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



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


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


3.notifyAll() 阳光普照



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



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


三、join 快来绑一绑timing



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



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


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


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


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


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


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


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


四、yield



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



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


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




END

0 个评论

要回复文章请先登录注册