黑马程序员——Java多线程—线程同步—wait、notify、notifyAll
来源:互联网 发布:已备案域名注册 编辑:程序博客网 时间:2024/05/09 11:24
参考:
CSDN 高爽:Java线程(三):线程协作-生产者/消费者问题
关于wait、notify、notifyAll如何使用不再说明,可以参考网上的教程,仅在此记录一个小问题。
先看一段代码,这段代码有一个问题:
package org.lgy.study.thread;import java.util.List;import java.util.ArrayList;/* javac -d classes "src/org/lgy/study/thread/WaitNotifyEgg.java"java org.lgy.study.thread.WaitNotifyEgg *//* 这个类用来测试线程同步(通信),不同线程在一个盘子中取放鸡蛋 */public class WaitNotifyEgg{public static void main(String[] args){// 盘子Plate p = new Plate();// 启动3个放鸡蛋的线程for(int i = 0; i < 3; i++){new Thread(new PutEggThread(p), "放-" + i).start();}// 启动3个取鸡蛋的线程for(int i = 0; i < 3; i++){new Thread(new GetEggThread(p), "取-" + i).start();}}/* 定义一个盘子类,用来存放鸡蛋 */public static class Plate{// 存放鸡蛋的盘子private List<Object> eggs = new ArrayList<>();// 放鸡蛋public synchronized void put(Object egg){if(eggs.size() > 0){System.out.println(Thread.currentThread().getName() + ",盘子已满,正在等待... ...");try{this.wait();}catch(InterruptedException e){e.printStackTrace();}}eggs.add(egg);System.out.println(Thread.currentThread().getName() + ",放入鸡蛋:" + egg);this.notify();}// 取鸡蛋public synchronized Object get(){if(this.eggs.size() == 0){System.out.println(Thread.currentThread().getName() + ",盘子为空,正在等待... ...");try{this.wait(); // 如果盘子为空,则阻塞当前取鸡蛋的线程}catch(InterruptedException e){e.printStackTrace();}}Object egg = eggs.get(0); // 拿到鸡蛋eggs.clear(); // 清空盘子System.out.println(Thread.currentThread().getName() + ",取到鸡蛋:" + egg);this.notify(); // 唤醒因当前对象(即this代表的Plate对象)而阻塞的线程return egg;}}/* 放鸡蛋的线程 */private static class PutEggThread implements Runnable{private Plate plate; // 往这个盘子里放鸡蛋private Object egg; // 被放入盘子中的鸡蛋private PutEggThread(Plate plate){this.plate = plate;// this.egg = new Object();}public void run(){while(true){this.egg = new Object();this.plate.put(egg);try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}}}}/* 取鸡蛋的线程 */private static class GetEggThread implements Runnable{private Plate plate; // 从这个盘子中取鸡蛋public GetEggThread(Plate plate){this.plate = plate;}public void run(){while(true){plate.get();try{Thread.sleep(100);}catch(InterruptedException e){e.printStackTrace();}}}}}
看一下上面代码的运行结果:
下面分析一下为什么会出现上面的错误。
出现问题的代码如下:
// 放鸡蛋public synchronized void put(Object egg){if(eggs.size() > 0){System.out.println(Thread.currentThread().getName() + ",盘子已满,正在等待... ...");try{this.wait();}catch(InterruptedException e){e.printStackTrace();}}eggs.add(egg);System.out.println(Thread.currentThread().getName() + ",放入鸡蛋:" + egg);this.notify();}
线程“放-1、放-2”是在上面的“this.wait()”处被阻塞的,当它们被唤醒时,就会从被阻塞的代码后面继续执行,也就是说它们会继续执行“this.wait()”后面的代码,即“eggs.add(egg)... ...”,直至所在方法结束,所以就出现了2个线程都可以向盘子中放鸡蛋的情况。
我们期望的情况是这样的,当被阻塞的线程被唤醒后,它先判断盘子中是否有鸡蛋,如果有那它再次阻塞,如果没有那它就放鸡蛋。所以我们应该把“this.wait()”放到一个不断判断盘子中是否有鸡蛋的循环中,如果有鸡蛋就执行循环(即阻塞线程),如果没有鸡蛋就退出循环,放鸡蛋。
修改后的代码:
// 放鸡蛋public synchronized void put(Object egg){while(eggs.size() > 0){System.out.println(Thread.currentThread().getName() + ",盘子已满,正在等待... ...");try{this.wait();}catch(InterruptedException e){e.printStackTrace();}}eggs.add(egg);System.out.println(Thread.currentThread().getName() + ",放入鸡蛋:" + egg);this.notify();}
对于取鸡蛋的get方法也做同样的处理。
问题二:死锁
这是基于问题一修改后的代码的运行结果:
分析一下死锁的原因:
先看一下程序的执行
1:“放-0” 因为没有线程阻塞,所以没有唤醒任何线程
2:“放-2” 阻塞
3:“放-1” 阻塞
4:“取-2” 唤醒“放-2”
5:“取-1” 阻塞
6:“取-0” 阻塞
7:“放-2” 唤醒“放-1”
8:“放-1” 阻塞
9:“放-0” 阻塞
10:“放-2” 阻塞
11:“取-2” 唤醒 “取-1”
12:“取-1” 阻塞
13:“取-2” 阻塞
名 初态 每次执行完后的状态
放-0:就绪 就绪 阻塞
放-1:就绪 阻塞(被唤醒)阻塞
放-2:就绪 阻塞(被唤醒)就绪阻塞
取-0:就绪 阻塞
取-1:就绪 阻塞(被唤醒)阻塞
取-2:就绪 就绪 就绪 阻塞
从上面的分析中可以看出,每一个线程的终态都是阻塞,所以发生了死锁。
发生死锁的原因就是唤醒线程时用的是notify,而不是notifyAll。notify在唤醒线程时是随机唤醒一个阻塞的线程,具有不确定性,有可能唤醒放鸡蛋的线程,也有可能唤醒取鸡蛋的线程。
从上面程序的执行过程可以看出,在输出8、9、10的结果后,三个放鸡蛋的线程全部阻塞掉了,这是应该有取鸡蛋的线程来唤醒放鸡蛋的线程,但是实际上取鸡蛋的线程唤醒的不是放鸡蛋的线程,而是唤醒了取鸡蛋的线程,被唤醒的取鸡蛋的线程因为没有鸡蛋可取而阻塞掉(直接阻塞掉是不会唤醒任何线程的)。
- 黑马程序员——Java多线程—线程同步—wait、notify、notifyAll
- [java多线程]多线程同步(二)——wait, notify, notifyAll, join以及sleep
- Java 同步方式 (2) —— wait和notify/notifyall
- Java 同步方式 (2) —— wait和notify/notifyall
- Java 同步方式 (2) —— wait和notify/notifyall
- Java 同步方式 (2) —— wait和notify/notifyall
- Java 同步方式 (2) —— wait和notify/notifyall
- Java多线程(九)——wait() notify() notifyAll()
- Java多线程——sleep(),wait(),notify(),notifyAll(),join()
- Java多线程5—死锁和wait()、notify()、notifyAll()
- Java多线程5—死锁和wait()、notify()、notifyAll()
- Java线程和多线程(二)——对象中的wait,notify以及notifyAll方法
- Java多线程㈤—②线程间通讯(wait、notify、notifyAll)
- 【java】线程同步(synchronized,wait,notify,notifyAll)
- java线程同步(synchronized,wait,notify,notifyAll)
- Java 线程同步(wait、notify、notifyAll)
- 线程间协作——wait、notify、notifyAll
- 线程间协作——wait & notify & notifyAll
- #define定义回调函数用法
- sql分级汇总
- C++基础—消息处理实例
- Asp.net C# 实现内容页调用模板页函数
- sqlserver存储过程
- 黑马程序员——Java多线程—线程同步—wait、notify、notifyAll
- Android Volley完全解析(一),初识Volley的基本用法
- iOS学习笔记之CoreGraphics框架
- Tomcat集群Cluster实现原理剖析
- gtest简介及简单使用
- linux如何查看CPU,内存,机器型号,网卡信息
- javascript检测窗口关闭
- linux grep 用法
- 算法导论-第2章 - 算法基础