Java wait和notify虚假唤醒原理

自己在此记录一下,方便日后复习。

虚假唤醒的概念

jdk官方文档解释:

所以说在wait和notify一块使用时,如果使用if作为条件时,会有虚假唤醒的情况发生,所以必须使用while作为循环条件。下面来举例实验:

首先,创建一个资源类:(在多线程中,一般都是资源类和线程操作解耦,不放在用同一个类中,只有在线程操作资源类时,才会创建资源类的对象)

package com.test;

/**
 * 资源类
 * @author Huxudong
 * @createTime 2020-04-01 21:57:39
 **/
public class Resource {
  /** 产品数 */
  private int product = 0;

  /** 进货 */
  public synchronized void get() {
    if(product >= 10) {
      System.out.println(Thread.currentThread().getName()+":"+"产品已满!");
      /** 当商品已经满的时候,进货线程挂起 */
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }

    /** 进货 */
    System.out.println(Thread.currentThread().getName()+":"+ ++product);
    /** 唤醒其他线程 */
    this.notifyAll();

  }

  /** 售货 */
  public synchronized void sale() {
    if(product <= 0) {
      System.out.println(Thread.currentThread().getName()+":"+"产品已空");
      try {
        this.wait();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    /** 售货 */
    System.out.println(Thread.currentThread().getName()+":"+ --product);
    /** 唤醒其他线程 */
    this.notify();
  }
}

然后再创建线程来操作我们的资源类(通过java8新特性Lambda表达式直接创建)

package com.test;

import java.util.concurrent.TimeUnit;

/**
 * 线程操作资源类,实现线程与资源类的解耦合
 * @author Huxudong
 * @createTime 2020-04-01 23:13:54
 **/
public class TestPc {
  public static void main(String[] args) {
    Resource resource = new Resource();
    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        try {
          /** 睡眠,便于观察结果 */
          TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        resource.get();
      }
    },"生产者A").start();

    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        resource.sale();
      }

    },"消费者C").start();

    new Thread(()->{
      for (int i = 0; i < 20; i++) {

        resource.get();
      }

    },"生产者B").start();

    new Thread(()->{
      for (int i = 0; i < 20; i++) {
        resource.sale();
      }

    },"消费者D").start();
  }
}

先来看看如果使用if条件会发生什么:

对,你没看错,怎么可能会出现负数呢,这肯定是不对的。冷静下来分析一下,还是有点头绪,知道哪里出现了问题的(那你是一个处事不惊的人,很厉害)。

来,分析一下,一开始先调用了消费者C,D线程(因为我们写了睡眠在生产者中),消费者此时发现此时product资源为0,所以,消费者C,D这两个兄弟,没办法只能调用wait方法,睡眠了,并且释放了锁。

但是此时第一个消费者已经苏醒了,发动机开始生产产品了,并且生产之后,又唤醒了所有等待的消费者线程。这消费者C,D两兄弟终于苏醒了,D哥们先获得了锁,所以就先消费了一个产品,然后就又发现没有产品了,又伤心的休眠去了,但是不要忘了,此时还有一个C哥们被唤醒了啊,你唤醒了人家,人家总的干点什么事情吧,不然这多难受,刚好不巧的是,此时的判断条件是if,所以此时C哥们便不受条件的约束,接着上面自己睡眠的代码处执行,毅然决然的又去消费了一个产品,原来D哥们消费后,就已经为0了,这个C哥们再去消费减一,不就是-1了吗,以此类推分析。发现如果判断条件用不好,此时唤醒的C哥们就相当于虚假唤醒的了,会给程序带来不可预估的错误。所以在这里判断必须要使用while,先来看看把if换成while的结果。

这回结果就比较正常了,为什么使用while就可以呢,因为像上文所说,即使唤醒了所有的消费者线程,此时会不停while循环判断,如果此时条件是为0,那么C哥们就不能出while,那么他也就不回执行下面消费产品的减减操作了,那么就会避免了这种错误。这也是官方提倡的在使用wait 和notifyAll的时候,必须使用while循环条件判断。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Java的wait(), notify()和notifyAll()使用心得

    wait(),notify()和notifyAll()都是java.lang.Object的方法:wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.notify(): Wakes up a single thread that is waiting on this object's

  • 详解Java程序并发的Wait-Notify机制

    Wait-Notify场景 典型的Wait-Notify场景一般与以下两个内容相关: 1. 状态变量(State Variable) 当线程需要wait的时候,总是因为一些条件得不到满足导致的.例如往队列里填充数据,当队列元素已经满时,线程就需要wait停止运行.当队列元素有空缺时,再继续自己的执行. 2. 条件断言(Condition Predicate) 当线程确定是否进入wait或者是从notify醒来的时候是否继续往下执行,大部分都要测试状态条件是否满足.例如,往队列里添加元素,队列已满

  • Java使用wait() notify()方法操作共享资源详解

    Java多个线程共享资源: 1)wait().notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写. 2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁,或者叫管程) 3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程: 4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线

  • Java 中Object的wait() notify() notifyAll()方法使用

    Java 中Object的wait() notify() notifyAll()方法使用 一.前言 对于并发编程而言,除了Thread以外,对Object对象的wati和notify对象也应该深入了解其用法,虽然知识点不多. 二.线程安全基本知识 首先应该记住以下基本点,先背下来也无妨: 同一时间一个锁只能被一个线程持有 调用对象的wait()和notify()前必须持有它 三.wait()和notify()理解 3.1 wait()和notify()方法简介 wait()和notify()都是

  • java多线程之wait(),notify(),notifyAll()的详解分析

    wait(),notify(),notifyAll()不属于Thread类,而是属于Object基础类,也就是说每个对象都有wait(),notify(),notifyAll()的功能.因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了. wait导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或被其他线程中断.wait只能由持有对像锁的线程来调用. notify唤醒在此对象监视器上等待的单个线程.如果所有线程都在此对象上等

  • 详细分析java线程wait和notify

    wait()和notify()是直接隶属于Object类,也就是说,所有对象都拥有这一对方法.初看起来这十分 不可思议,但是实际上却是很自然的,因为这一对方法阻塞时要释放占用的锁,而锁是任何对象都具有的,调用任意对象的 wait() 方法导致线程阻塞,并且该对象上的锁被释放.而调用任意对象的notify()方法则导致因调用该对象的wait() 方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行). 其次,wait()和notify()可在任何位置调用,但是这一对方法却必须在

  • Java多线程基础 线程的等待与唤醒(wait、notify、notifyAll)

    本篇我们来研究一下 wait() notify() notifyAll() . DEMO1: wait() 与 notify() public class Test { static class ThreadOne extends Thread { private Callback mCallback; @Override public void run() { work(); if (mCallback != null) { mCallback.onResult(false); } } //

  • 浅谈Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.先来我们来看下相关定义: wait() :调用该方法的线程进入WATTING状态,只有等待另外线程的通知或中断才会返回,调用wait()方法后,会释放对象的锁. wait(long):超时等待最多long毫秒,如果没有通知就超时返回. notify() :通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提

  • Java wait和notify虚假唤醒原理

    自己在此记录一下,方便日后复习. 虚假唤醒的概念 jdk官方文档解释: 所以说在wait和notify一块使用时,如果使用if作为条件时,会有虚假唤醒的情况发生,所以必须使用while作为循环条件.下面来举例实验: 首先,创建一个资源类:(在多线程中,一般都是资源类和线程操作解耦,不放在用同一个类中,只有在线程操作资源类时,才会创建资源类的对象) package com.test; /** * 资源类 * @author Huxudong * @createTime 2020-04-01 21:

  • Java线程通信及线程虚假唤醒知识总结

    线程通信 线程在内部运行时,线程调度具有一定的透明性,程序通常无法控制线程的轮换执行.但Java本身提供了一些机制来保证线程协调运行. 假设目前系统中有两个线程,分别代表存款和取钱.当钱存进去,立马就取出来挪入指定账户.这涉及到线程间的协作,使用到Object类提供的wait().notify().notifyAll()三个方法,其不属于Thread类,而属于Object,而这三个方法必须由监视器对象来调用: synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,因此

  • java并发等待条件的实现原理详解

    前言 前面介绍了排它锁,共享锁的实现机制,本篇继续学习AQS中的另外一个内容-Condition.想必学过java的都知道Object.wait和Object.notify,同时也应该知晓这两个方法的使用离不开synchronized关键字.synchronized是jvm级别提供的同步原语,它的实现机制隐藏在jvm实现中.作为Lock系列功能中的Condition,就是用来实现类似 Object.wait和Object.notify 对应功能的. 使用场景 为了更好的理解Lock和Condit

  • Java 线程状态和等待唤醒机制和线程池的实现

    1.概念 线程一共有6中状态,相互之间可以互相转换. 等待唤醒案例(线程之间的通信) 实现: 等待唤醒案例:线程之间的通信 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait方法,放弃cpu的执行,进入到WAITING状态(无限等待) 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify方法,唤醒顾客吃包子 注意: 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行 同步使用的锁对象必须保证唯一 只有锁对象才能调用wait和noti

  • 一文带你搞懂Java中Synchronized和Lock的原理与使用

    目录 1.Synchronized与Lock对比 2.Synchronized与Lock原理 2.1 Synchronized原理 2.2 Lock原理 3.Synchronized与Lock使用 Synchronized Lock 4.相关问题 1.Synchronized与Lock对比 实现方式:Synchronized是Java语言内置的关键字,而Lock是一个Java接口. 锁的获取和释放:Synchronized是隐式获取和释放锁,由Java虚拟机自动完成:而Lock需要显式地调用lo

  • Java线程中的notifyAll唤醒操作(推荐)

    注意: java中的notifyAll和notify都是唤醒线程的操作,notify只会唤醒等待池中的某一个线程,但是不确定是哪一个线程,notifyAll是针对指定对象里面的所有线程执行唤醒操作,指定对象一旦唤醒成功.则会立即加入线程的资源争夺中去. 例如: package TestThread.ThreadSynchronized; public class TestWaitAll { public static void main(String[] args) { Test1 test1

  • Java 实现多线程切换等待唤醒交替打印奇偶数

    引言 在日常工作生活中,可能会有用时几个人或是很多人干同一件事,在java编程中,同样也会出现类似的情况,多个线程干同样一个活儿,比如火车站买票系统不能多个人买一到的是同一张票,当某个窗口(线程)在卖某一张票的时候,别的窗口(线程)不允许再卖此张票了,在此过程中涉及到一个锁和资源等待的问题,如何合理正确的让线程与线程在干同一件事的过程中,不会抢资源以及一个一直等待一个一直干活的状况,接下来就聊一下多线程的等待唤醒以及切换的过程,在此就以A和B两个线程交替打印奇偶数的例子为例,代码如下: pack

  • Java util concurrent及基本线程原理简介

    一:什么是JUC JUC就是java.util.concurrent下面的类包,专门用于多线程的开发. 二:进程和线程的区别 进程是可并发执行的程序在某个数据集合上的一次计算活动,也是操作系统进行资源分配和调度的基本单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与 同属一个进程的其他的线程共享进程所拥有的全部资源. 并发:多线程. 多个线程操

  • java并发中DelayQueue延迟队列原理剖析

    介绍 DelayQueue队列是一个延迟队列,DelayQueue中存放的元素必须实现Delayed接口的元素,实现接口后相当于是每个元素都有个过期时间,当队列进行take获取元素时,先要判断元素有没有过期,只有过期的元素才能出队操作,没有过期的队列需要等待剩余过期时间才能进行出队操作. 源码分析 DelayQueue队列内部使用了PriorityQueue优先队列来进行存放数据,它采用的是二叉堆进行的优先队列,使用ReentrantLock锁来控制线程同步,由于内部元素是采用的Priority

  • Java同步关键字synchronize底层实现原理解析

    目录 1 字节码层实现 1.1 InterpreterRuntime::monitorenter 1.1.1 函数参数 JavaThread *thread 1.1.2 函数体 2 偏向锁 2.1 偏向锁的意义 2.2 偏向锁的获取 2.2.1 markOop mark = obj->mark() 2.2.2 判断mark是否为可偏向状态 2.2.3 判断mark中JavaThread的状态 2.2.4 通过CAS原子指令 2.2.5 如果执行CAS失败 2.3 偏向锁的撤销 2.4 轻量级锁

随机推荐