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的更多相关文章

  1. Java Concurrency - wait & notify, 等待通知机制

    生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...

  2. java使用wait(),notify(),notifyAll()实现等待/通知机制

    public class WaitNotify { static boolean flag=true; static Object lock=new Object(); static class Wa ...

  3. Java多线程学习(四)等待/通知(wait/notify)机制

    转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79690279 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...

  4. 线程之间通信 等待(wait)和通知(notify)

    线程通信概念: 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程之间的通信就成为整体的必用方式之一.当线程存在通信指挥,系统间的交互性会更强大,在提高CPU利用率的同 ...

  5. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  6. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  7. 一 java线程的等待/通知模型

    java 中线程之间的通信问题,有这么一个模型:一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行又是另一个线程.前者是生产者,后者就是消费者 ...

  8. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  9. Java多线程之三volatile与等待通知机制示例

    原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...

随机推荐

  1. 前端笔记之jQuery(下)事件&节点操作&净位置&拖拽&页面卷动值&遍历JSON

    一.监听事件大全 1.1 JavaScript事件 onblur 元素失去焦点 onchange 用户改变域的内容 onclick 鼠标点击某个对象 ondblclick 鼠标双击某个对象 onfoc ...

  2. 前端笔记之JavaScript(十一)event&BOM&鼠标/盒子位置&拖拽/滚轮

    一.事件对象event 1.1 preventdefault()和returnValue阻止默认事件 通知浏览器不要执行与事件关联的默认动作. preventdefault()  支持Chrome等高 ...

  3. LeetCode专题-Python实现之第26题:Remove Duplicates from Sorted Array

    导航页-LeetCode专题-Python实现 相关代码已经上传到github:https://github.com/exploitht/leetcode-python 文中代码为了不动官网提供的初始 ...

  4. Magicodes.NET框架之路——产品之路(谈谈产品管理)

    虽然Magicodes.NET现在还不属于产品,但是却不妨碍她想成为产品的心. 为什么突然有了此篇,这篇不是空穴来风,而是我思考良久的结果: 为了让大家知道我在干什么,我想干什么,我将要干什么还有我干 ...

  5. ubuntu开发项目不能执行热更新

    当项目开发到一定成熟度,项目基本上比较大(vue,angular,react,java,php等),在Ubuntu系统环境下,我们写了代码,但是不能想Windows一样执行热更新,这是因为Ubuntu ...

  6. php7 闭包调用

    早起的版本如 PHP5.6 ,绑定并调用闭包使用 bindTo,而PHP7 中 Closure :: call()方法具有更好的性能,废话不多说, 较早的 PHP 示例: <?php class ...

  7. https处理的一个过程,对称加密和非对称加密

    一,对称加密 所谓对称加密,就是它们在编码时使用的密钥e和解码时一样d(e=d),我们就将其统称为密钥k. 对称加解密的过程如下: 发送端和接收端首先要共享相同的密钥k(即通信前双方都需要知道对应的密 ...

  8. charles抓包出现乱码 SSL Proxying not enabled for this host:enable in Proxy Setting,SSL locations

    1.情景:抓包的域名下 全部是unknown,右侧出现了乱码 2.查看unknown的notes里面:SSL Proxying not enabled for this host:enable in ...

  9. image_channel_data_type含义

    在穿件image对象的时候,需要传入一个cl_image_format参数,该参数结果包含image_channel_order和image_channel_data_type两个成员.前一个成员表示 ...

  10. OpenCL的buffer以及sub-buffer

    buffer,sub-buffer和image对比 相同点:都是OCL memory对象 维度 特性关键词 buffer 一维 array of bytes sub-buffer 一维 views i ...