等待通知--wait notify
1、简单理解
在jdk1.5之前用于实现简单的等待通知机制,是线程之间通信的一种最原始的方式。考虑这样一种等待通知的场景:A B线程通过一个共享的非volatile的变量flag来实现通信,每当A线程观察到flag为true的时候,代表着有工作需要做,A线程处理任务然后吧flag改成false。B线程负责发布任务,每次由B线程把flag改为false。
import org.junit.Test;
import org.junit.runner.RunWith; public class ThreadCom_02 {
private static boolean flag = false;
private static final Object lock = "lock"; public static class Worker implements Runnable{ @Override
public void run() {
while (true){
synchronized (lock){ //获得锁之前先检查条件是否成立,如果不成立直接wait把锁释放了
while (!flag){
System.out.println("没有任务,等待.....");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} //成立才继续执行,执行完毕后修改共享变量,并notifyAll
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("完成任务");
flag = false;
lock.notifyAll();
break;
}
}
}
} public static class Boss implements Runnable{ @Override
public void run() {
while (true){
synchronized (lock){
while (flag){
System.out.println("此时有任务,我就不发布任务了....");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} System.out.println("发布任务");
flag = true;
lock.notifyAll();
}
break;
}
}
} @Test
public static void main(String[] args) throws InterruptedException {
Thread boss = new Thread(new Boss());
Thread worker = new Thread(new Worker());
worker.start();
Thread.sleep(10);
boss.start();
} }
2、注意
- 在jdk1.5之后尽量不要去使用wait notify来实现诸如生产者消费者这种线程之间的通信模式,而是去使用JUC下的工具类。因为后者更强大,更便捷。
- wait方法永远在循环里调用。
synchronized(lock){
while(condition){
lock.wait()
}
//do something
}
while方法判断的条件和wait方法像一个屏障一样,只有在条件满足的时候才会执行后续代码,否则就调用wait进入等待队列并释放锁。比如在消费者中线程,如果队列为空那么消费者线程不能继续执行。那为什么一定是while来判断呢?为什么if不可以呢?
当睡眠的线程被notify唤醒且获得锁后就会继续接着wait执行,如果是while那么还会再判断一次condition,如果是if就直接接着往下执行。即while会在wait返回后再次判断,if不会再次判断。那为什么需要再次判断呢?或者说在什么情况下一个调用了wait方法的线程苏醒后仍然不满足继续执行的条件?
- 线程伪唤醒。一个sleep状态的线程会莫名其妙的但是概率很低的醒过来。
- 恶意或者错误通知。当条件不成立的时候调用了notify或者条件仅仅允许一个线程苏醒的时候调用了notifyAll。
以生产者消费者模式为例,一般会使用一个共享的队列当做通信媒介。当队列中元素数目为空的时候,消费者线程如果获得了处理机资源那么也只能wait,否则消费者线程消费一个元素并notifyAll;当队列中元素满的时候,生产者线程如果获得了处理机也只能wait,否则生产者向队列里增加一个元素并notifyAll.正是notifyAll的使用导致了错误通知。
比如队列长度为1,设置了1个生产者,5个消费者。当生产者添加了一个元素使队列满了,生产者wait后释放锁。5个消费者有一个消费者抢到了锁并消费了这一个元素,然后调用notifyAll,问题就处在这个地方,调用notifyAll不仅会唤醒生产者线程,也会唤醒那四个没有获得锁的消费者线程。如果这4个消费者线程被唤醒的时候没有使用while判断condition是否成立,那么一定会出现错误的情况。
import java.util.LinkedList;
import java.util.Queue; public class ThreadCon_03 {
private static Queue<Integer> queue = new LinkedList<>(); public static class Consumer implements Runnable{ @Override
public void run() {
while (true){
synchronized (queue){
while (queue.isEmpty()){
System.out.println(Thread.currentThread().getName()+"队列为空,等待");
try {
queue.wait();
System.out.println(Thread.currentThread().getName()+"被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
} queue.remove();
System.out.println(Thread.currentThread().getName()+"消费一个...");
queue.notifyAll();
}
}
}
} public static class Producer implements Runnable{ @Override
public void run() {
int count = 0;
while (count<5){
count++;
synchronized (queue){
while (queue.size()>=1){
System.out.println("队列满了");
try {
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
} } queue.add(1);
System.out.println("添加一个");
queue.notifyAll();
System.out.println("=======================");
}
}
}
} public static void main(String[] args) throws InterruptedException {
for (int i=0;i<5;i++){
Thread thread = new Thread(new Consumer());
thread.start();
}
Thread.sleep(10);
Thread producer = new Thread(new Producer());
producer.start();
} }
控制台打印可以看到每次抢到锁的消费者线程调用notifyAll之后会唤醒所有的消费者线程。
Thread-0队列为空,等待
Thread-1队列为空,等待
Thread-2队列为空,等待
Thread-3队列为空,等待
Thread-4队列为空,等待
添加一个
=======================
队列满了
Thread-4被唤醒
Thread-4消费一个...
Thread-4队列为空,等待
Thread-3被唤醒
Thread-3队列为空,等待
Thread-2被唤醒
Thread-2队列为空,等待
Thread-1被唤醒
Thread-1队列为空,等待
Thread-0被唤醒
Thread-0队列为空,等待
添加一个
=======================
队列满了
Thread-0被唤醒
Thread-0消费一个...
Thread-0队列为空,等待
Thread-1被唤醒
Thread-1队列为空,等待
Thread-2被唤醒
Thread-2队列为空,等待
Thread-3被唤醒
Thread-3队列为空,等待
Thread-4被唤醒
Thread-4队列为空,等待
添加一个
=======================
队列满了
Thread-4被唤醒
Thread-4消费一个...
Thread-4队列为空,等待
Thread-3被唤醒
Thread-3队列为空,等待
Thread-2被唤醒
Thread-2队列为空,等待
Thread-1被唤醒
Thread-1队列为空,等待
Thread-0被唤醒
Thread-0队列为空,等待
添加一个
=======================
队列满了
Thread-0被唤醒
Thread-0消费一个...
Thread-0队列为空,等待
Thread-1被唤醒
Thread-1队列为空,等待
Thread-2被唤醒
Thread-2队列为空,等待
Thread-3被唤醒
Thread-3队列为空,等待
Thread-4被唤醒
Thread-4队列为空,等待
添加一个
=======================
Thread-4被唤醒
Thread-4消费一个...
Thread-4队列为空,等待
Thread-3被唤醒
Thread-3队列为空,等待
Thread-2被唤醒
Thread-2队列为空,等待
Thread-1被唤醒
Thread-1队列为空,等待
Thread-0被唤醒
Thread-0队列为空,等待 Process finished with exit code 130 (interrupted by signal 2: SIGINT)
所有根据上面的分析,如果我把消费者的while换成if,那么一定会越界。
总之,为了安全起见wait方法一定要写在while里,来保证wait方法返回后还需要一次判断。
3、参考
http://www.importnew.com/26584.html
等待通知--wait notify的更多相关文章
- Java Concurrency - wait & notify, 等待通知机制
生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...
- java使用wait(),notify(),notifyAll()实现等待/通知机制
public class WaitNotify { static boolean flag=true; static Object lock=new Object(); static class Wa ...
- Java多线程学习(四)等待/通知(wait/notify)机制
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- 线程之间通信 等待(wait)和通知(notify)
线程通信概念: 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程之间的通信就成为整体的必用方式之一.当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同 ...
- java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader
1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...
- 二 Java利用等待/通知机制实现一个线程池
接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1 定义一个任务的接口 ...
- 一 java线程的等待/通知模型
java 中线程之间的通信问题,有这么一个模型:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程.前者是生产者,后者就是消费者 ...
- java多线程系列(三)---等待通知机制
等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...
- Java多线程之三volatile与等待通知机制示例
原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...
随机推荐
- Linux基础知识第六讲,远程管理ssh操作
目录 Linux基础知识第六讲,远程管理ssh操作 一丶什么是SSH 1.什么是SSH 2.了解域名跟端口 二丶SSH命令以及远程连接linux进行维护 1.ssh命令格式 2.scp远程终端拷贝文件 ...
- Java开发知识之Java数组
Java开发知识之Java数组 一丶数组简介 首先,不管是Java 还是 C++ 还是其它语言.都有数组. (有可能叫法不同) 数组简而言之就是存储一段连续相同数据类型的数据结构 在Java中数组可以 ...
- CAN总线学习记录之二:系统结构与帧结构
CAN总线系统结构 CAN 控制器 接收控制单元中微处理器发出的数据,处理数据并传给 CAN 收发器 CAN 收发器 将数据传到总线 or 从总线接收数据给 CAN 控制器 CAN 数据传递终端 避 ...
- lua的String
基础字符串函数 字符串库中有一些函数非常简单,如: 1). string.len(s) 返回字符串s的长度: 2). string.rep(s,n) 返回字符串s重复n次的结果: 3 ...
- JSON字符串反序列化成对象_部分属性值反序列化失败
简介:本人在开发webapi接口时遇到了:一个复杂的Json字符串在反序列化为对象时报,无法发序列化其中的一个属性对象? 使用方法: InternalRecommendRequestFormModel ...
- centos7 Failed to start firewalld.service: Unit is masked.
centos7 启动防火墙失败:Failed to start firewalld.service: Unit is masked. ---- 刚yum安装了iptables 解决: 执行”sys ...
- jQuery each和js forEach用法比较
本文实例分析了jQuery each和js forEach用法.分享给大家供大家参考,具体如下: 对于遍历数组的元素,js代码和jquery都有类似的方法,js用的是forEach而jquery用的是 ...
- python 练习 simple_server 判断路径及返回函数
函数 routers 返回一个 urlpatterns 元组,里面包含了路径名和函数名:在 函数 application 中遍历 urlpatterns 元组,路径存在则返回函数名,不存在则返回 40 ...
- #WEB安全基础 : HTML/CSS | 0x7HTML5和W3C验证
标准,标准,什么都有标准 你听说过HTML5吗?这是一个新版本,当然也有新标准 我只准备了一个index.html文件 以下是代码 <!DOCTYPE html> <!--告诉浏览器 ...
- Ubuntu 16.04安装Zabbix 3.2 版本
系统环境:ubuntu16.04 注意:为了便于实验测试,需要关闭防火墙: parallels@zabbix-server:~$ sudo systemctl stop ufw parallels ...