notify notifyAll 死锁
从一个死锁分析wait,notify,notifyAll
本文通过wait(),notify(),notifyAll()模拟生产者-消费者例子,说明为什么使用notify()会发生死锁。
1. 代码示例
1.1 生产者
public class Producer implements Runnable {
List<Integer> cache;
public Producer(List<Integer> cache) {
this.cache = cache;
}
@Override
public void run() {
while (true) {
produce();
}
}
private void produce() {
synchronized (cache) {
try {
while (cache.size() == 1) {
cache.wait();
}
// 模拟一秒生产一条消息
Thread.sleep(1000);
cache.add(new Random().nextInt());
cache.notify();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.2 消费者
public class Consumer implements Runnable {
List<Integer> cache;
public Consumer(List<Integer> cache) {
this.cache = cache;
}
@Override
public void run() {
while (true) {
consume();
}
}
private void consume() {
synchronized (cache) {
try {
while (cache.isEmpty()) {
cache.wait();
}
System.out.println("Consumer consumed [" + cache.remove(0) + "]");
cache.notify();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
1.3 测试代码
1.3.1 一个生产者一个消费者
public class WaitNotifyTest {
public static void main(String[] args) throws Exception {
List<Integer> cache = Lists.newArrayList();
new Thread(new Consumer(cache)).start();
new Thread(new Producer(cache)).start();
}
}
运行结果:
Consumer consumed [169154454]
Consumer consumed [511734]
Consumer consumed [-25784306]
Consumer consumed [1648046130]
……
从控制台可以看出,每隔一秒钟消费一条数据,完美的生产者消费者模型!
1.3.2 多个消费者多个生产者
既然生产者和消费者都继承了Runnable,就应该多线程运行嘛~~
public class WaitNotifyTest {
public static void main(String[] args) throws Exception {
List<Integer> cache = Lists.newArrayList();
new Thread(new Consumer(cache)).start();
new Thread(new Consumer(cache)).start();
new Thread(new Consumer(cache)).start();
new Thread(new Producer(cache)).start();
new Thread(new Producer(cache)).start();
new Thread(new Producer(cache)).start();
}
}
运行结果:
Consumer consumed [-1658797021]
Consumer consumed [-2050633449]
程序运行一会后,控制台就没输出了!!! 通过jstack命令可以分析出当前demo发生了死锁。
1.3.3 将Consumer和Producer中的notify()换成notifyAll()试试
运行结果
Consumer consumed [-781807640]
Consumer consumed [42787175]
Consumer consumed [327937050]
Consumer consumed [2140968760]
……
程序又可以欢乐的跑起来了!
我到底做错什么了!
2. notify和notifyAll的区别
1.3.2和1.3.3的不同之处只是将notify换成了notifyAll,肯定是这里有问题
在说明notify和notifyAll的区别之前,先阐述两个概念:锁池和等待池
- 锁池:假设线程A已经拥有了某个对象(注意:不是类)的锁,而其它的线程想要调用这个对象的某个synchronized方法(或者synchronized块),由于这些线程在进入对象的synchronized方法之前必须先获得该对象的锁的拥有权,但是该对象的锁目前正被线程A拥有,所以这些线程就进入了该对象的锁池中。
- 等待池:假设一个线程A调用了某个对象的wait()方法,线程A就会释放该对象的锁后,进入到了该对象的等待池中。
再说notify和notifyAll的区别
- 线程调用了对象的 wait()方法,便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁(即不会参与线程调度,大家理解就好~~)。
- notifyAll调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。
- notify调用后,只会将等待池中的一个随机线程移到锁池。
知道上面的原理后,1.3.x的例子就很好理解了
1.3.1:因为只有一个生产者和消费者,所以等待池中始终只有一个线程,要么就是生产者唤醒消费者,要么消费者唤醒生产者,所以程序可以成功跑起来;
1.3.2:为了简化分析过程,假设两个消费者线程C1、C2,一个生产者线程P1。
时序过程如下(只是一种可能性,不绝对,毕竟只是个例子嘛~~):
- C1,C2观察到缓存cache中无数据,进入等待池;
- P1获取锁并设置cache数据,通过notify唤醒等待池中某个线程C1,假设C1被唤醒并放入锁池,然后P1释放锁、继续循环重新获取锁并因为检测到cache.size()==1而进入等待池;
- 此时锁池中的线程为C1,C1会竞争到锁,从而消费数据,然后执行notify方法并释放锁,并假设其notify方法会将C2从等待池移入锁池;
- C2检测到cache为空,执行await()使自身进入锁池。因为自身的阻塞所以不能唤醒C1或P1,从而导致死锁!
1.3.3:分两种情况分析:
- cache为空
- 先假设所有线程P都一直停留在等待池。但是这种情况是不可能存在的,因为cache肯定是某个C线程消费后才为空,然后该C线程会执行notifyAll方法将所有等待池中的线程都移入锁池,所以不可能所有P线程一直在等待池;
- 既然P线程不可能一直在等待池,那么这种P线程会竞争锁从而设置cache的值导致cache不为空;
- cache不空的情况分析过程同上
3 使用wait、notify的基本套路
下面是effective java中推荐的标准写法:
synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
// Perform action appropriate to condition
}
为什么要用while,改成if行不行,就像下面这样:
synchronized (obj) {
if (<condition does not hold>)
obj.wait(timeout);
// Perform action appropriate to condition
}
我们将1.1和1.2代码中的while换成if,启动一个生产者,两个消费者线程,某个消费者线程会出现数组下标越界的异常,代码及原因分析如下:
public class WaitNotifyTest {
public static void main(String[] args) throws Exception {
List<Integer> cache = Lists.newArrayList();
new Thread(new Consumer(cache)).start();
new Thread(new Consumer(cache)).start();
Thread.sleep(1000);
new Thread(new Producer(cache)).start();
}
}
- 消费者C1、C2发现cache为空,相继进入等待池;
- P1生产数据,放入cache并唤醒C1,同时自己进入等待池;
- C1消费数据,唤醒C2,C2从cache.wait()处开始执行,因为我们将while(cache.isEmpty())改成了if(cache.isEmpty()),C2不会再次检查cache是否为空,而是直接执行后续代码,这时cache的数据已经被C1消费完了,调用cache.get(0)产生数组下标越界!
notify notifyAll 死锁的更多相关文章
- 零基础学习java------day18------properties集合,多线程(线程和进程,多线程的实现,线程中的方法,线程的声明周期,线程安全问题,wait/notify.notifyAll,死锁,线程池),
1.Properties集合 1.1 概述: Properties类表示了一个持久的属性集.Properties可保存在流中或从流中加载.属性列表中每个键及其对应值都是一个字符串 一个属性列表可包含另 ...
- wait notify notifyAll await signal signalAll 的理解及示例
从常见的一道面试题开始,题目的描述是这样子的: 有三个线程分别打印A.B.C,请用多线程编程实现,在屏幕上循环打印10次ABCABC- 网上大都教了你怎么去实现,其实我也写过一篇 https://bl ...
- java 多线程之wait(),notify,notifyAll(),yield()
wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对像都有wait(),notify(),notifyAll()的功能.因为都个对像都 ...
- java中的wait(),notify(),notifyAll(),synchronized方法
wait(),notify(),notifyAll()三个方法不是Thread的方法,而是Object的方法.意味着所有对象都有这三个方法,因为每个对象都有锁,所以自然也都有操作锁的方法了.这三个方法 ...
- java 多线程(wait/notify/notifyall)
package com.example; public class App { /* wait\notify\notifyAll 都属于object的内置方法 * wait: 持有该对象的线程把该对象 ...
- Java多线程8:wait()和notify()/notifyAll()
轮询 线程本身是操作系统中独立的个体,但是线程与线程之间不是独立的个体,因为它们彼此之间要相互通信和协作. 想像一个场景,A线程做int型变量i的累加操作,B线程等待i到了10000就打印出i,怎么处 ...
- 使用Object的wait,notify,notifyAll做线程调度
我们知道java中的所有类的祖先都是Object,Object类有四个个方法wait(),wait(long timeout),notify(),notifyAll(),这四个方法可以用来做线程的调度 ...
- Java多线程之wait(),notify(),notifyAll()
在多线程的情况下,因为同一进程的多个线程共享同一片存储空间,在带来方便的同一时候,也带来了訪问冲突这个严重的问题.Java语言提供了专门机制以解决这样的冲突,有效避免了同一个数据对象被多个线程同一时候 ...
- Java多线程的wait(),notify(),notifyAll()
在多线程的情况下.因为多个线程与存储空间共享相同的过程,同时带来的便利.它也带来了访问冲突这个严重的问题. Java语言提供了一种特殊的机制来解决这类冲突,避免同一数据对象由多个线程在同一时间访问. ...
随机推荐
- Redhat Enterprise Linux 7.4/CentOS 7.4 安装后初始化配置
由于我是最小化安装,需要在安装后进行一些配置 1. 设定启动级别 [root@home ~]# systemctl set-default multi-user.target 2. 设定网络 [roo ...
- ECCV 2014 Results (16 Jun, 2014) 结果已出
Accepted Papers Title Primary Subject Area ID 3D computer vision 93 UPnP: An optimal O(n) soluti ...
- 关于Hyper-V备份的四大注意事项
尽管Hyper-V备份相对简单,但备份管理员仍需注意四大问题.这四方面的问题在创建备份时可能不太重要,但在备份恢复时影响甚大. 1.对于虚拟机来说不仅意味着虚拟磁盘 就目前来看,企业在执行Hyper- ...
- Save a 32-bit Bitmap as 1-bit .bmp file in C#
What is the easiest way to convert and save a 32-bit Bitmap to a 1-bit (black/white) .bmp file in C# ...
- Visual Studio中Debug和Release的区别
在Visual Studio中,生成应用程序的时候有2种模式:Debug和Release.两者之间如何取舍呢? 假设有这么简单的一段代码,在主程序中调用方法M1,M1方法调用M2方法,M2方法调用M3 ...
- 爱普生Me330 打印机改装连供系统计划
Me330想改装连供得大家可以看一下,本文是我的亲生经历,现分享给大家,希望能给你们提供帮助,如果有不懂的地方可以联系我Email: 事先说明,我不买连供,也不卖这款机子,购买的话,请不要打扰我!& ...
- mysql数据库字符集初步理解
1.MySQL(4.1以后版本) 服务器中有六个关键位置使用了字符集的概念,他们是: 1.client 2.connection 3.database 4.results 5.server 6.sys ...
- Easing圆环动画
Easing圆环动画 效果 源码 https://github.com/YouXianMing/Animations // // CircleView.h // YXMWeather // // Cr ...
- tsort - 拓扑排序
tsort - 拓扑排序 本文链接:http://codingstandards.iteye.com/blog/834572 (转载请注明出处) 用途说明 tsort命令通常用于解决一种逻辑问题, ...
- 基于MINA实现server端心跳检测(KeepAliveFilter)
MINA自带了对心跳协议的支持,可以对心跳做出细致的配置,本文在次基础上实现了server端对client端的心跳检测. 在开始之前先简单介绍下keepAlive的机制: 首先,需要搞清楚TCP ke ...