我们还是通过源代码和代码注释来学习这个问题

我们先来看看wait方法的注释,这里截取最根源的native方法给的注释

Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.

大意是调用wait的线程进入等待状态(WAITING和TIME_WAITING),正常情况下有三种机会唤醒,一个是notify随机唤醒第二个是notifyAll(),第三种等待时间混过去

The current thread must own this object's monitor.

This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread T becomes disabled for thread scheduling purposes and lies dormant until one of four things happens:

Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.

Some other thread invokes the notifyAll method for this object.

Some other thread interrupt() interrupts Thread T.

The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.

这一段很重要,我简单翻译一下。

调用 wait 方法的线程必须拥有此对象的监视器。

该方法将当前线程(称为 T)置于此对象的 WaitSet 中,然后放弃该对对象的锁。直到发生以下四种情况之一,该线程才会被唤醒:

  1. 其他线程为此对象调用了 notify 方法,并且线程 T 恰好被操作系统选择为要唤醒的线程。
  2. 其他线程为此对象调用了 notifyAll 方法,唤醒了所有线程。
  3. 其他一些线程 interrupt() 中断了线程 T。
  4. 超过了指定的等待时间(当然,如果 timeout 为零,线程会一直等待直到被通知)。

我们在中简单介绍过上锁原理,本质上就是每个对象的头部都定义一个monitor,也就是这个对象的“控制权”,因此我们在调用wait之前,必须让这个monitor起作用(告诉编译器要使用monitor的意思),就必须使用synchronized中。

我们在看看notifyAll和notify的注解注释

The thread T is then removed from the wait set for this object and re-enabled for thread scheduling. It then competes in the usual manner with other threads for the right to synchronize on the object;

Once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, on return from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked

简单翻译下,即是只要线程T被唤醒(上面说的四种情况),那么这个线程就从WAITSET中移除,再次加入竞争锁的行为中,注意是还是要竞争的!!!一旦又抢到了锁,那么它对对象的所有同步声明都将恢复到调用 wait 方法时的状态,可以接着往下执行。说人话就是,wait让线程等一等,先释放掉拥有的锁,notify/notifyAll就是让凉快地候着的线程重新加入竞争锁的行动中。

中简单概括为,避免虚假唤醒和长无效唤醒。

我先解释下什么是无效唤醒,这里简单写一下生产者消费者说明下场景(错误的)

生产者消费者伪代码
class Producer{
count++;
notify();
}
class Cosumer{
while(count<=0){
wait();
count--;
}
}

这里试想下这种情况,cpu先运行消费者线程检查count满足≤0即(while(count<=0))这行代码,然后cpu跑去运行生产者线程,运行完notify()唤醒其余线程,但并没有线程wait,然后cpu去运行消费者,消费者从while的下一行继续运行(上下文切换),运行wait(),这时Cosumer就睡觉了,如果后面没有唤醒就一直睡觉了。

再解释下虚假唤醒(Spurious Wakeup),这个词也出现在源码的注解里,这里用金鱼生存场景来模拟一下虚假唤醒这一问题的严重性,这个场景设计要求是金鱼必须吃一次且只能吃一次,一个人喂了金鱼另一个人就不能再喂,金鱼吃完才能继续投喂,这其实就是一个生产者消费者模型

代码多次运行查看结果
package org.joseph.MultiThread.demo;

/**
* @author joseph
* @date 2023/01/30
* @description
**/
public class WaitNotSynchronized { int count=0; public synchronized void produce() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"进行投喂");
if(count>0){
System.out.println("已经投喂过了,等鱼吃完再投喂!");
this.wait();
}
count++;
System.out.println(Thread.currentThread().getName()+"投喂成功"+",现在食物有"+count+"个");
System.out.println(Thread.currentThread().getName()+"唤醒其余工作者和鱼");
this.notifyAll();
}
public synchronized void consumer() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"吃东西");
if(count<=0){
System.out.println("没有东西吃,等待投喂者投喂!");
this.wait();
}
count--;
System.out.println(Thread.currentThread().getName()+"吃成功了"+",现在食物有"+count+"个");
System.out.println(Thread.currentThread().getName()+"唤醒其余工作者和鱼");
this.notifyAll();
} public static void main(String[] args) throws Exception{
WaitNotSynchronized waitNotSynchronized = new WaitNotSynchronized();
Runnable feed=new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
try {
waitNotSynchronized.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Runnable eat=new Runnable() {
@Override
public void run() {
for(int i=0;i<10;i++){
try {
waitNotSynchronized.consumer();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}; Thread thread1 = new Thread(feed, "投喂者1号");
Thread thread2 = new Thread(feed, "投喂者2号");
Thread thread3 = new Thread(eat, "鱼1号");
Thread thread4 = new Thread(eat, "鱼2号");
thread1.start();
thread2.start();
thread3.start();
thread4.start();
} }

理论上水中的食物只有0或者1的存在,但出现了-1,说明出现了问题,我简单解释下,出现-1的场景是由于存在多个生产者和消费者,那么在消费者1号机工作完,会进行唤醒其他线程操作,那么很有可能是消费者2号机被唤醒,注意消费者2号机是由于上一次wait处于阻塞状态,所以消费者2号机会继续运行同步代码剩下的内容!!!

返回看wait的源码注释中这句话“Once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked.”唤醒后是恢复到调用wait时的状态,也就是说pc计数器中的指针是从wait后代码开始运行,所以为了避免这种问题,if改成while!!!

总结:

wait需要写在synchronized同步块中,理由有两个

1.wait的原理就是获得对象的监听器,而每个对象头部都有monitor的预备,synchronized的原理也是在代码上下添加monitorenter和monitorexit的指令,添加synchronized相当于高速编译器我要是用monitor的功能,没有synchronized,wait就失效了

2.wait的格式最好按照官方文档的要求写,这样的好处避免无效唤醒和虚假唤醒。

为什么wait()需要在同步代码块内使用的更多相关文章

  1. 彻底理解线程同步与同步代码块synchronized

    public class Demo { public static synchronized void fun1(){ } public synchronized void fun2(){ } pub ...

  2. About 静态代码块,普通代码块,同步代码块,构造代码块和构造函数的纳闷

    构造函数用于给对象进行初始化,是给与之对应的对象进行初始化,它具有针对性,函数中的一种.特点:1:该函数的名称和所在类的名称相同.2:不需要定义返回值类型.3:该函数没有具体的返回值.记住:所有对象创 ...

  3. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  4. 对象及变量的并发访问(同步方法、同步代码块、对class进行加锁、线程死锁)&内部类的基本用法

    主要学习多线程的并发访问,也就是使得线程安全. 同步的单词为synchronized,异步的单词为asynchronized 同步主要就是通过锁的方式实现,一种就是隐式锁,另一种是显示锁Lock,本节 ...

  5. java中的synchronized同步代码块和同步方法的区别

    下面这两段代码有什么区别? //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized ...

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

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

  7. 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁

    什么是同步 在上一篇0036 Java学习笔记-多线程-创建线程的三种方式示例代码中,实现Runnable创建多条线程,输出中的结果中会有错误,比如一张票卖了两次,有的票没卖的情况,因为线程对象被多条 ...

  8. 2016/9/25编写java实验报告时对synchronized(同步代码块)的一些感悟

    通过此次实验,明白了多线程的设置和启动.synchronized代码块的用法.线程的优先级使用方法.知道了那几类资源是线程共享的. 我现在理解的多线程是:实例化一个继承了Thread类或实现了Runn ...

  9. Java基础之线程——管理线程同步代码块(BankOperation4)

    控制台程序. 除了同步类对象的方法之外,还可以把程序中的语句或代码块制定为synchronized,这种方式更强大,因为可以指定哪个对象从语句或代码块的同步中获益,而不像同步方法那样仅仅是包含代码的对 ...

  10. Android(java)学习笔记68:同步代码块 和 同步方法 的应用

    1. 同步代码块 和 同步方法 代码示例: (1)目标类,如下: package cn.himi.text; public class SellTicket implements Runnable { ...

随机推荐

  1. 快学起来!python入门自学必看

    记得刚开始学python的时候,各种买书各种找资料,最后发现资料找了一大堆,但都是东一块西一块,内容不全且不系统,无意间发现这个宝藏网站,真的是太全了,当作工具书,时不时的翻翻,总会发现一些惊喜.0基 ...

  2. 微信小程序通过经纬度计算两点之间距离

    小程序中通过经纬度计算两点之间的距离km 1.拾取两地经纬度坐标 . data:{ //当前定位位置 latitude: null, longitude: null, // 目的地坐标 latitud ...

  3. 记一次因为关键字OUT 导致的后台"sql injection violation" 报错的问题

    在navicat和mssm中执行用字段别名'out'均没有问题,但是在mybatis里使用就会报 "sql injection violation, syntax error: ERROR. ...

  4. 获取gps

    package com.example.myapplication;import android.Manifest;import android.annotation.SuppressLint;imp ...

  5. YII oracle

    以 11.2 为例 , 注意必须要与数据库版本对应下载如下两个文件instantclient-basic-linux.x64-11.2.0.4.0.zip https://download.oracl ...

  6. 三、JMeter实战-快速上手JMeter

    1.JMeter基本操作 线JMeter最基本的操作有三个步骤: 先添加一个线程组. 添加HTTP请求. 添加查看结果树. 1.1.添加线程组 在测试计划下新建一个线程组 1.2.添加HTTP请求 在 ...

  7. mapreduce启动命令

    mapreduce启动命令 hadoop jar /var/lib/hadoop-hdfs/codejar/flash_format_testip.jar com.js.dataclean.hm2_h ...

  8. linux-taglist

    vim 变量.函数索引 1. sudo dnf install vim-taglist 2. 下载taglist, https://www.vim.org/scripts/script.php?scr ...

  9. apache axis2 生成客户端实体类

    打开控制台,cd至压缩包(见下方网盘链接)的bin目录下执行如下命令.只用到了axis2-1.4.1-bin.zip. war目前不知道有什么用,因为资源不好找,留着备用吧 WSDL2Java -ur ...

  10. 使用 Transformers 在你自己的数据集上训练文本分类模型

    最近实在是有点忙,没啥时间写博客了.趁着周末水一文,把最近用 huggingface transformers 训练文本分类模型时遇到的一个小问题说下. 背景 之前只闻 transformers 超厉 ...