Java 杂谈

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

2018-09-03  本文已影响1人  LandHu

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

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

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

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

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

先了解两个概念

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

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

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

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

5.notify产生死锁的场景

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

上一篇下一篇

猜你喜欢

热点阅读