Overview这篇文章有些东西是我自己的理解,比如JDK为什么一开始是这样设计的,初衷是什么,还有我没有找到相关资料,只能说说自己的理解,大家看完文章再说说自己的看法吧。我觉得把实现部分解释清楚问题不大。代码在这里。如果你理解它,你就会知道是怎么回事。大家都知道Object.wait/notify(All)主要是协同线程处理,大家都用的比较多。逻辑类似于下面的用法。看到上面的代码,是不是有疑惑呢?至少我有几个问题你会问自己:*为什么在进入wait和notify的时候需要加同步锁?由于加了synchronized锁,当一个线程调用wait时,显然还在synchronized块中。其他线程如何进入锁执行notify呢?*为什么wait方法可能会抛出InterruptedException*如果多个线程进入wait状态,一个线程会不会调用notify来唤醒线程,以便唤醒那些wait线程*wait的线程是当一个线程执行完notify会不会之后立即觉醒?*notifyAll是如何实现全唤醒的?*wait的线程会不会影响负载?如果以上问题是你想知道的,那么这篇文章或许会给你答案。为什么要加同步锁从实现上看,这个锁很重要。就是因为有这个锁,才能把整个wait/notify玩完。当然我觉得类似的机制也可以通过其他方式来实现,但是hotspot至少是完全依赖这个锁来实现wait/notify的。如果我们要实现这个机制,我们会怎么做呢?我们知道wait/notify是为线程间协作而设计的。我们执行wait的时候让线程挂掉,执行notify的时候唤醒其中一个挂起的线程。线程,需要有一个地方来保存对象和线程的映射关系(你可以想象一个映射,key是一个对象,value是一个线程列表)。当调用该对象的wait方法时,将当前线程放入该线程链表中,当调用该对象的notify方法时,从线程链表中取出一个线程让其继续执行。这个好像是可行的,也比较简单。现在的问题是把这个映射关系放在哪里。Synchronized也是为线程间协作而设计的,它要解决上面遇到的问题。可能因为如此,wait和notify的实现直接依赖于synchronized(jvm规范中要求实现monitorenter/monitorexit)来实现,这只是我的理解,也许初衷并不是这个原因,这个是其实这篇文章很久没写的原因之一,因为我拿不出证据证明我的理解是正确的,欢迎大家在这里说说自己的看法。其他线程如何进入同步块的问题其实在没有退出同步块的情况下执行wait方法后回答起来很简单,因为在等待过程中同步锁会被暂时释放,但是需要注意的是当一个线程调用notify唤起这个线程,当wait方法退出时,wait方法退出前会重新获取锁,只有获取到锁后才会继续执行。想象一下,我们知道wait方法被monitorenter和monitorexit包围着。当我们执行wait方法时如果释放了锁,出来的时候没有拿锁,那么执行monitorexit指令时会发生什么?当然这样可以兼容,但是实现起来还是很奇怪。大家应该都知道为什么wait方法可能会抛出InterruptedException。当我们调用一个线程的interrupt方法时,对应的线程就会抛出这个异常。wait方法并不想打破这个规则,所以即使当前线程因为wait已经阻塞了,当一个线程想要它起来继续执行的时候,它还是要从阻塞状态中恢复过来,所以wait方法会当它被唤醒时检测到这个状态,当一个线程中断它时,它会抛出这个异常来从阻塞状态中恢复。这里需要注意两点:*如果被中断的线程只是创建而没有启动,启动后进入等待状态后不会恢复*如果被中断的线程已经启动,在进入wait之前,如果一个线程调用它的interrupt方法,那么这个wait就相当于什么都不做,会直接跳出来,不会阻塞被通知的线程(All)。这里有规定吗?,先进入wait的线程会先被唤醒*如果线程是通过nootifyAll唤醒的,默认是先进入的线程会被唤醒,即执行完后进先出策略notify后立即唤醒线程。其实大家可以通过这个来验证,在notify之后写一些逻辑,看看这些逻辑是在其他线程被唤醒之前还是之后执行的。这是一个细节问题,你可能没有注意这一点。事实上,hotspot中真正的实现是在你退出同步块的时候。它实际上会唤醒相应的线程,不过这也是默认的策略,也是可以改变的,notify后立即唤醒相关线程。notifyAll是如何实现完全唤醒的可能大家立马觉得这很简单,一个for循环就可以搞定,但是在jvm中就没那么简单了,需要借助monitorexit。上面提到当一个线程从wait状态中恢复过来的时候,首先要获取锁,然后退出同步块,所以notifyAll的实现是调用notify的线程唤醒最后一个进入wait的线程退出其同步块时的状态,然后在该线程退出同步块时继续。唤醒倒数第二个进入等待状态的线程,以此类推。同样,这是一个战略问题。jvm提供了一个一个直接唤醒线程的参数,但是很少见就不提了。wait的线程是否会影响负载可能是大家比较关心的话题,因为这关系到系统性能。wait/nofity是通过jvm中的park/unpark机制实现的。在linux下,这个机制是通过pthread_cond_wait/pthread_cond_signal来发挥的,所以当线程进入wait状态时,实际上会放弃cpu,也就是说这类线程不会占用cpu资源。【本文为专栏作家李嘉鹏原创文章,转载请微信公众号(你个假笨蛋,id:lovestblog)联系作者授权转载】点此查看本作者更多好文
