等待通知--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与等待通知机制示例
原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...
随机推荐
- oracle数据库之plsql可视化操作建表
首先登录PL/SQL developer. 点击工具栏中的第一个图标,选择“表”. 右边会弹出一个窗口,我们以可视化方式来创建一个Table. 如下图所示,在“一般”选项卡中,输入“名称” ...
- Chapter 5 Blood Type——30
That wasn't a challenge; I was always pale, and my recent swoon had left a light sheen of sweat on m ...
- spring cloud 配置zuul实用
在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 技术背景 前面我们通过Ribbon或Feign实现了微服务之间的调用和负载均衡 ...
- Once More
Topic Link http://ctf5.shiyanbar.com/web/more.php 1)源代码分析 发现 ereg()函数使得password必须是数字或字母同时长度必须是小于8val ...
- 第48章 UserInfo端点(UserInfo Endpoint) - Identity Server 4 中文文档(v1.0.0)
UserInfo端点可用于检索有关用户的身份信息(请参阅规范). 调用者需要发送代表用户的有效访问令牌.根据授予的范围,UserInfo端点将返回映射的声明(至少需要openid作用域). 示例 GE ...
- .NetCore教程之 EFCore连接Mysql DBFirst模式
一:创建EF的类库,同时将此项目设置为启动项(为Scaffold-DbContext -tables指令使用),同时安装2个包 ①Microsoft.EntityFrameworkCore.Too ...
- linux下tomcat启动很慢的解决办法
1.用vim编辑器打开tomcat的bin目录下的catalina.sh [root@iz09a32x1sghz3z bin]# vi /usr/local/src/java/tomcats/tomc ...
- 设计模式之Builder建造者模式 代码初见
public class EmployeeBuilder { private int id = 1; private string firstname = "first"; pri ...
- 面试题之(HTTP协议)【转】
转自:http://www.cnblogs.com/ranyonsue/p/5984001.html HTTP简介 HTTP协议是Hyper Text Transfer Protocol(超文本传输协 ...
- [JS设计模式]:工厂模式(3)
简单工厂模式是由一个方法来决定到底要创建哪个类的实例, 而这些实例经常都拥有相同的接口. 这种模式主要用在所实例化的类型在编译期并不能确定, 而是在执行期决定的情况. 说的通俗点,就像公司茶水间的饮料 ...