JVM源码分析之Object.wait/notify(All)彻底解读

本文来自: PerfMa技术社区

PerfMa(笨马网络)官网linux

概述

本文其实一直都想写,由于各类缘由一直拖着没写,直到开公众号的第一天,有朋友再次问到这个问题,此次让我静心下来准备写下这篇文章,本文有些东西是我本身的理解,好比为何JDK一开始要这么设计,初衷是什么,没怎么去找相关资料,因此只能谈谈本身的理解,因此你们看到文章以后能够谈谈本身的见解,对于实现部分我倒以为说清楚问题不大,code is here,看明白了就知道怎么回事了。网络

Object.wait/notify(All)你们都知道主要是协同线程处理的,你们用得也不少,大概逻辑和下面的用法差很少jvm

image.png

看到上面代码,你会有什么疑惑吗?至少我会有几个问题会问本身: 为何进入wait和notify的时候要加synchronized锁 既然加了synchronized锁,那当某个线程调用了wait的时候明明还在synchronized块里,其余线程怎么进入到锁里去执行notify的 为何wait方法可能会抛出InterruptedException异常 若是有多个线程都进入wait状态,那某个线程调用notify唤醒线程时是否按照顺序唤起那些wait线程 wait的线程是在某个线程执行完notify以后立马就被唤起吗 notifyAll又是怎么实现全唤起的 * wait的线程是否会影响load性能

若是上面这些问题也都是你想了解的,那这篇文章或许能给你一个答案。学习

为什么要加synchronized锁

从实现上来讲,这个锁相当重要,正由于这把锁,才能让整个wait/notify玩转起来,固然我以为其实经过其余的方式也能够实现相似的机制,不过hotspot至少是彻底依赖这把锁来实现wait/notify的。spa

若是要咱们来实现这种机制咱们会怎么去作,咱们知道wait/notify是为了线程间协做而设计的,当咱们执行wait的时候让线程挂起,当执行notify的时候唤醒其中一个挂起的线程,那须要有个地方来保存对象和线程之间的映射关系(能够想象一个map,key是对象,value是一个线程列表),当调用这个对象的wait方法时,将当前线程放到这个线程列表里,当调用这个对象的notify方法时从这个线程列表里取出一个来让其继续执行,这样看来是可行的,也比较简单,那如今的问题这种映射关系放到哪里。而synchronized正好也是为线程间协做而设计的,上面碰到的问题它也要解决,或许正由于这样wait和notify的实现就直接依赖synchronzied(monitorenter/monitorexit是jvm规范里要求要去实现的)来实现了,这只是个人理解,可能初衷不是这个缘由,这其实也是这篇文章迟迟未写的一个缘由吧,由于我没法取证本身的理解是对的,欢迎各位在这块谈谈本身的看法。线程

wait方法执行后未退出同步块,其余线程如何进入同步块

这个问题其实要回答很简单,由于在wait处理过程当中会临时释放同步锁,不过须要注意的是当某个线程调用notify唤起了这个线程的时候,在wait方法退出以前会从新获取这把锁,只有获取了这把锁才会继续执行,想象一下,咱们知道wait的方法是被monitorenter和monitorexit包围起来,当咱们在执行wait方法过程当中若是释放了锁,出来的时候又不拿锁,那在执行到monitorexit指令的时候会发生什么?固然这能够作兼容,不过这实现起来仍是很奇怪的。设计

为何wait方法可能抛出InterruptedException异常

这个异常你们应该都知道,当咱们调用了某个线程的interrupt方法时,对应的线程会抛出这个异常,wait方法也不但愿破坏这种规则,所以就算当前线程由于wait一直在阻塞,当某个线程但愿它起来继续执行的时候,它仍是得从阻塞态恢复过来,所以wait方法被唤醒起来的时候会去检测这个状态,当有线程interrupt了它的时候,它就会抛出这个异常从阻塞状态恢复过来。code

这里有两点要注意: 若是被interrupt的线程只是建立了,并无start,那等他start以后进入wait态以后也是不能会恢复的 若是被interrupt的线程已经start了,在进入wait以前,若是有线程调用了其interrupt方法,那这个wait等于什么都没作,会直接跳出来,不会阻塞对象

被notify(All)的线程有规律吗

这里要分状况: 若是是经过notify来唤起的线程,那先进入wait的线程会先被唤起来 若是是经过nootifyAll唤起的线程,默认状况是最后进入的会先被唤起来,即LIFO的策略

notify执行以后立马唤醒线程吗

其实这个你们能够验证一下,在notify以后写一些逻辑,看这些逻辑是在其余线程被唤起以前仍是以后执行,这个是个细节问题,可能你们并无关注到这个,其实hotspot里真正的实现是退出同步块的时候才会去真正唤醒对应的线程,不过这个也是个默认策略,也能够改的,在notify以后立马唤醒相关线程。

notifyAll是怎么实现全唤起的

或许你们立马想到这个简单,一个for循环就搞定了,不过在jvm里没实现这么简单,而是借助了monitorexit,上面我提到了当某个线程从wait状态恢复出来的时候,要先获取锁,而后再退出同步块,因此notifyAll的实现是调用notify的线程在退出其同步块的时候唤醒起最后一个进入wait状态的线程,而后这个线程退出同步块的时候继续唤醒其倒数第二个进入wait状态的线程,依次类推,一样这这是一个策略的问题,jvm里提供了挨个直接唤醒线程的参数,不过都很罕见就不提了。

wait的线程是否会影响load

这个或许是你们比较关心的话题,由于关乎系统性能问题,wait/nofity是经过jvm里的park/unpark机制来实现的,在linux下这种机制又是经过pthread_cond_wait/pthread_cond_signal来玩的,所以当线程进入到wait状态的时候实际上是会放弃cpu的,也就是说这类线程是不会占用cpu资源。

一块儿来学习吧

PerfMa KO 系列课之 JVM 参数【Memory篇】

Hotspot GC研发工程师也许漏掉了一块逻辑

相关文章
相关标签/搜索