大话Java线程通信

“怎么还慢吞吞的!”,JVM翘着二郎腿在调度室里大声喊道。“Thread-2,你已经被我创建出来了,赶紧干活!”。

启动三个线程,一个生产者Thread-2,两个消费者Thread-0和Thread-1

Thread-2这才反应过来,看看自己的身体,是个结构分明、线条优美的线程栈。先看看自己有啥东西:一个程序计数器、一连串的函数栈帧。程序计数器记录程序执行到哪里了,函数栈帧是一个方法的信息体,里面包含局部变量表、操作数栈等。


线程示意图

“好的,老板。我看到我有一个run方法,里面调用了produce()方法,用人类的话说,我应该是个生产者啊”。JVM点了点头,这小子挺机灵的。“那你赶紧去生产产品吧,消费者线程打电话催了好几次了”。


我是个生产者线程

生产者线程的run方法

消费者线程,那是个啥?

跟你一样,也是由我创建出来的一个线程。只是职责与你不同,你负责生产产品,它负责消费产品。

消费者线程

它在哪里,我怎么看不见。

你们彼此都是有独立空间的,但是你们需要协作完成工作,你得注意两点,①操作临界区数据时要进行互斥(同一时刻只能有一个线程操作这个共享变量),②修改后你得通知其他线程可以操作这个共享变量了;

你叫Producer类,你有一个放在方法区的实例变量taskQueue(List<Integer> taskQueue),你往这个容器中放东西,消费者线程的Consumer类也要持有同一个实例变量,这样它们就可以从这个容器里取产品了。JVM怕它不懂,多解释了几句。

看老大停顿了,天性活泼多问的Thread-2正准备开口接着问...

“先调用produce()函数栈帧,走一遍指令再说”。JVM没搭理它,发出了指令。

生产产品

过了1ms,活泼的Thread-2又打电话来了。我已经走了一遍了,刚开始让我判断是否生产到5个了,如果到了就调用wait()方法。因为数量为0所以就继续走下面的逻辑,生产了一个产品放在taskQueue中,然后调用了notify()方法。调用这两个方法的时候,到底发生了什么啊?

你这大大咧咧的性格,怎么忘了描述最重要的那点了呢?

Thread-2摸摸自己红扑扑的脸蛋,生怕老板骂它。

你刚开始运行的时候,系统是不是让你去抢占taskQueue的monitor,你首先得抢到这个实例变量的monitor,你才能运行。才有后面调用wait()或notify()的事。
这些都是synchronized在起作用,它的作用是在代码块前后加上monitorenter和monitorexit两个字节码指令,这样就不会有其他线程同时操作taskQueue这个变量。这个就是我们常说的互斥锁。当然synchronized可以加在方法上或者包裹一个代码块,那说来就话长了。

我想起来了,我在taskQueue的对象头中确实看到了是我持有了锁。执行taskQueue.wait()或taskQueue.notify()方法的时候,是需要持有这个对象的monitor的。


对象的内存布局示意图

是的,其中wait()、notify()这样的方法是要在synchronized关键字包裹的中才能执行的,否则会抛出IllegalMonitorStateException异常。

那这两个方法的作用是啥咧?

你先生产到5个产品。

JVM刚准备看会新闻,Thread-2来电了。我生产5个了,调用了wait()方法,释放了monitor。我现在身子就好像僵硬了一样,什么都干不了。JVM哈哈哈一笑,你这是把自己放在了一个等待池中,等待taskQueue实例变量的锁。让我跟你说说现在外面都发生了什么吧。

还记得你生产第一个产品的时候,就调用了notify()方法吧。这个方法会通知其他的线程来消费产品。但是他们没有进来消费,因为你还没有释放taskQueue的monitor,等到你生产满5个后,会调用wait()方法,这个方法会让你释放掉monitor,并进入等待池。
消费者线程那边也是同样的逻辑,他们会在收到通知后去竞争taskQueue的monitor,竞争到的线程开始进入consume()方法,它同样需要加互斥锁。当他们消费完了之后,会调用wait()方法,你就有机会去争夺taskQueue的monitor。抢到以后,你就可以继续工作了。

消费产品

最终的结果你可以看下:

执行结果

Thread-0先运行,发现集合为空,则进入等待池。生产者生产产品,达到5个后进入wait状态,释放taskQueue的monitor,消费者线程Thread-0获得taskQueue的monitor进入运行状态。以此类推消费线程Thread-1的行为。

JVM似乎很中意这个刚出生的娃娃。“看你天赋异禀,老哥赐你锦囊一幅,当你迷茫的时候,记得拿出来看看”。

锦囊第一法:搬完砖了怎么休息?

调用wait方法,它是Object类的方法,final native修饰,即本地方法,不可被子类重写!必须在sychronized块中调用;
作用:当前线程Thread T释放对象锁,并将自己放在等待池(wait set)中。直到其他线程获取这个对象的monitor控制权,以及发生以下四种情况之一时,线程Thread T将被唤醒:
①其他线程持有同一个对象的monitor并调用notify()方法
②其他线程持有同一个对象的monitor并调用notifyAll()方法
③超过等待时间
④被其他线程打断

调用wait方法的要点:

  • 当前线程必须拥有这个对象的monitor,否则会抛出IllegalMonitorStateException异常;
  • 当前线程将自己放在wait set中,并解除所有在这个对象上的同步声明;
  • 当前线程调用wait方法后返回,线程及对象的同步状态与调用wait方法时是一致的;
  • 当被唤醒时,会与其他线程一起竞争获取对象的同步权(获取monitor);

怎么用?

线程的唤醒缘故不一定是上面提到的四种情况,有时候可能会是假唤醒状态,所以需要在轮询+条件判断的代码块中使用,在不满足条件时,让线程一直等待:

    synchronized (obj) {
        while (condition does not hold)
        obj.wait(timeout);
         ... // Perform action appropriate to condition
     }

锦囊第二法:怎么通知其他线程小伙伴?

调用notify()或者notifyAll()方法。它们都是Object类的final native方法,必须在sychronized块中调用;
作用:notify是唤醒等待相同对象monitor的其中一个线程Thread T(Thread T须调用了wait方法),至于是哪个线程被唤醒,这要取决于具体实现,我们无法判断。notifyAll是唤醒所有等待相同对象monitor的线程们。其他跟notify一样。

Thread T不会立马进入运行状态:

①当前调用了notify()方法后,当前线程不一定会立马释放对象的monitor,直到当前线程的同步块执行完成后才会释放;
②Thread T得到可以唤醒的通知,对象的monitor也被释放了,此时会与其他等待当前对象monitor的对象一同竞争这个monitor,获取到这个对象的monitor后方可进入运行状态。

像这样用就可以了:

   synchronized(lockObject){
      //establish_the_condition;
      lockObject.notify();
      //any additional code if needed
}

Thread-2就这样慢慢熟悉了如何与其他线程进行协作,保证数据安全地运行,后来它了解到人类为了写出线程安全的代码,需要考虑到原子性、可见性、有序性等。看来又有新东西可以学了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容

  • 本文主要讲了java中多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。 首先讲...
    李欣阳阅读 2,433评论 1 15
  • Java多线程学习 [-] 一扩展javalangThread类 二实现javalangRunnable接口 三T...
    影驰阅读 2,946评论 1 18
  • 本文出自 Eddy Wiki ,转载请注明出处:http://eddy.wiki/interview-java.h...
    eddy_wiki阅读 2,041评论 0 14
  • 人生的大多数遇见 都会不可避免的分离 愿你在我看不见的地方 过得比从前更好
    冰玄海棠阅读 92评论 0 1
  • Lamp环境下配置域名 本地安装的虚拟机我们可以通过ifconfig命令来查看ip,通过ip在浏览器中访问我们页面...
    曹渊说创业阅读 1,325评论 0 0