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. 版本管理工具Git(三)Gitlab高可用

    高可用模式 企业版 社区版 我们这里说一下成本比较低的主备模式,它主要依赖的是DRBD方式进行数据同步,需要2台ALL IN ONE的GitLab服务器,也就是通过上面安装方式把所有组件都安装在一起的 ...

  2. 使用MaxCompute Java SDK运行安全相关命令

    使用MaxCompute Console的同学,可能都使用过MaxCompute安全相关的命令.官方文档上有详细的MaxCompute安全指南,并给出了安全相关语句汇总.   简而言之,权限管理.列级 ...

  3. 4.镜像管理【Docker每天5分钟】

    Docker给PaaS世界带来的“降维打击”,其实是提供了一种非常便利的打包机制.该机制打包了应用运行所需要的整个操作系统,从而保证了本地环境和云端环境的高度一致,避免了用户通过“试错”来匹配不同运行 ...

  4. mybatis-generator自动生成代码插件使用详解

    mybatis-generator是一款在使用mybatis框架时,自动生成model,dao和mapper的工具,很大程度上减少了业务开发人员的手动编码时间,今天自己研究了一下,也分享一下使用心得供 ...

  5. jquery/js知识点收藏

    1]关于页面跳转 "window.location.href"."location.href"是本页面跳转 "parent.location.href ...

  6. Maven(十二)Maven 依赖详解

    依赖的传递性 注意1:在Eclipise创建的Maven项目,若依赖eclipse空间中其他自己创建的 的项目时,此时并不会报错,但是当执行mvn compile命令时还是会显示缺失败.所以依赖的其他 ...

  7. Lucene.Net3.0.3+盘古分词器学习使用

    一.Lucene.Net介绍 Lucene.net是Lucene的.net移植版本,是一个开源的全文检索引擎开发包,即它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索 ...

  8. Maven项目POM文件错误,提示“Plugin execution not covered by lifecycle configuration”的解决方案

    一. 问题 Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-depend ...

  9. CSS代码片段

    定位: 将元素居中 将元素水平居中 将元素垂直居中 样式: 文字毛玻璃效果 -------------------------------------------代码----------------- ...

  10. FaaS技术框架

    FaaS介绍 微服务(MicroService)是以专注于单一服务/功能的小型单元块为基础,利用模块化的方式组合成复杂的大型应用服务. FaaS是Function as a Service的缩写,可以 ...