美文网首页Java 杂谈
谈谈notify()、notifyAll()、wait()、sl

谈谈notify()、notifyAll()、wait()、sl

作者: LandHu | 来源:发表于2018-09-03 19:21 被阅读1次

    1.wait()、sleep()的区别

    • sleep()方法属于Thread类中的,而wait()方法,则是属于Object类中的。

    • 调用 sleep()方法导致了程序暂停执行指定的时间,让出cpu该其他线程,但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态,线程不会释放对象锁
      调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用notify()方法后本线程才进入对象锁定池准备,获取对象锁进入运行状态。

    2.notify()和notifyAll()的区别

    • 线程调用了对象的 wait()方法,便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁,即不会参与线程调度
    • notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。
    • notify调用后,只会将等待池中的一个随机线程移到锁池。

    3.为什么wait()、notify()和notifyAll()是 Object类 中的方法?

    由于Thread类继承了Object类,所以Thread也可以调用者三个方法,等待和唤醒必须是同一个锁。而锁可以是任意对象,所以可以被任意对象调用的方法是定义在object类中。

    4.为什么wait,notify和notifyAll要与synchronized一起使用?

    先了解两个概念

    • 每一个对象都有一个与之对应的监视器
    • 每一个监视器里面都有一个该对象的锁和一个等待队列和一个同步队列

    wait()方法的语义有两个,一是释放当前对象锁,另一个是进入阻塞队列,可以看到,这些操作都是与监视器(锁)相关的,要指定一个监视器才能完成这个操作

    notify()方法也是一样的,用来唤醒一个线程,你要去唤醒,首先你得知道他在哪儿,所以必须先找到该对象,也就是获取该对象的锁,当获取到该对象的锁之后,才能去该对象的对应的等待队列去唤醒一个线程。值得注意的是,只有当执行唤醒工作的线程离开同步块,即释放锁之后,被唤醒线程才能去竞争锁。

    notifyAll()方法和notify()一样,只不过是唤醒等待队列中的所有线程

    之所以wait(),notify(),notifyAll()都必须使用在同步中,因为要它们对持有监视器(锁)的线程操作,因为只有同步才具有锁

    5.notify产生死锁的场景

    • 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
    • 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。

    wait()而导致阻塞的线程是放在等待池中的,因竞争失败导致的阻塞是放在锁池中的,notify()/notifyAll()实质上是把等待池中的线程放到锁池中去

    生产者数量为1,消费者数量为2,缓冲区为1场景:

    1. C1,C2观察到缓存cache中无数据,进入等待池;
    2. P1获取锁并设置cache数据,通过notify唤醒等待池中某个线程C1,假设C1被唤醒并放入锁池,然后P1释放锁、继续循环重新获取锁并因为检测到cache.size()==1而进入等待池;
    3. 此时锁池中的线程为C1,C1会竞争到锁,从而消费数据,然后执行notify方法并释放锁,并假设其notify方法会将C2从等待池移入锁池;
    4. C2检测到cache为空,执行wait()使自身进入等待池,因为自身的阻塞所以不能唤醒C1或P1,死锁产生!

    生产者、消费者数量都为2,缓冲区为1场景:

    1. C1获得锁,发现cache为0,wait(wait自动释放锁);
    2. C2获得锁,发现cache为0,wait(wait自动释放锁);
    3. P1获得锁,发现cache为0可以生产,生产以后放在cache,notify,现在cache为1;
    4. 第3步notify唤醒了C1,但是C1没有抢到锁,锁被P2拿到了;
    5. P2发现缓冲区为1(因为只是唤醒了C1,但是C1没有抢到锁,没法消费),wait(wait自动释放锁);
    6. 现在C1获得了锁,消费并且notify(此时P2和C2都处于wait状态),cache为0;
    7. 很不幸,第6步notify唤醒了C2,C2醒来后拿到锁发现cache为0,接着wait,与此同时,P2也在wait,死锁产生。

    可以看出问题的关键在于两个地方,一个是第4步notify并不能保证notify唤醒的线程获得锁,一个是第7步notify可能会唤醒同一种角色的线程。

    可以用Lock/Condition解决,两个Condition可以保证notify(signal)不同角色的线程,也可以用notifyAll解决,使线程间变成对锁的竞争。

    6.为什么多线程情况下建议使用while而不是if?

    原因:在线程中notify或者notifyAll会唤醒一个或多个线程,当线程被唤醒后,被唤醒的线程继续执行阻塞后的操作。
    多消费者时 当某个线程得到锁时队列为空,此时它应该wait,下次被唤醒时(任意线程调用notify),队列可能还是空的。因为有可能其他线程清空了队列。如果此时用的是if它将不再判断队列是否为空,直接继续,这样就引起了错误。但如果用while则每次被唤醒时都会先检查队列是否为空再继续,避免空指针或其他生产事故;生产也是同一个道理。


    技术讨论 & 疑问建议 & 个人博客

    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 许可协议,转载请注明出处!

    参考:

    https://blog.csdn.net/liuchuanyangyan/article/details/56668153

    相关文章

      网友评论

        本文标题:谈谈notify()、notifyAll()、wait()、sl

        本文链接:https://www.haomeiwen.com/subject/cqsbwftx.html