多线程 - wait 和 notify

wait

wait(等待)/notify(通知)
线程在操作系统上的调度是随机的
多个线程,需要控制线程之间执行某个逻辑的先后顺序,就可以让后执行的逻辑,使用wait,先执行线程,完成某些逻辑之后,通过notify唤醒对应的wait
另外,通过wait notify也是为了解决"线程饿死"问题

针对上述问题,同样也可以使用wait/notify来解决
让1号滑稽,拿到锁的时候进行判定
判定当前能否执行"取钱”操作.如果能护行,就正常执行
如果不能执行呢,就需要主动释放锁并且"阻塞等待"(通过调用wit),此时这个线程就不会在后续参与锁的竞争了,一直阻塞到"取钱”的条件具备了,此时,再由其他线程通过通知机制(notify)唤醒这个线程
 

public class Demo23 {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        System.out.println("wait 之前");
        obj.wait();
        System.out.println("wait 之后");
    }
}

public class Demo23 {
    public static void main(String[] args) throws InterruptedException {
        Object obj = new Object();
        System.out.println("wait 之前");
        synchronized (obj) {
            obj.wait();
        }
        System.out.println("wait 之后");
    }
}

由于代码中没有notify,所以wait一直持续等待下去~~

notify

wait使调用的线程进入阻塞,notify则是通知wait的线程被唤醒(另一个线程调用的)
被唤醒的wait就会重新竞争锁,并且在拿到锁之后,再继续执行
wait一共做了三件事
1)释放锁
2)进入阻塞等待,准备接受通知
3)收到通知之后,被唤醒,并且重新尝试获取锁
 

import java.util.Scanner;

public class Demo24 {
    private static Object locker = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t1 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t1 wait 之后");
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t2 notify 之前");
                Scanner sc = new Scanner(System.in);
                sc.next();//此处用户输入啥都行,主要是通过这个 next ,构造“阻塞”

                synchronized (locker){
                    locker.notify();
                }

                System.out.println("t2 notify 之后");
            }
        });

        t1.start();
        t2.start();
    }
}

一定要确保持有锁才能谈释放

notify唤醒wait的线程,假设多个线程,如果多个线程都在同一个对象上wait,此时notify是如何唤醒呢?
随机唤醒其中的一个线程

import java.util.Scanner;

public class Demo25 {
    private static Object locker = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t1 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 wait 之后");
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t2 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 wait 之后");
            }
        });
        Thread t3 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t3 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 wait 之后");
            }
        });
        Thread t4 = new Thread(() -> {
            Scanner sc = new Scanner(System.in);
            sc.next();
            System.out.println("t4 notify 之前");
            synchronized (locker) {
                locker.notify();
            }
            System.out.println("t4 notify 之后");
        });

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

和notify相对,还有一个操作,notifyAll唤醒所有等待的线程

唤醒是唤醒一个,还是唤醒所有,大部分的情况,使用唤醒一个的
一个一个唤醒(多执行几次notify)整个程序执行过程是比较有序的
如果一下唤醒所有,这些被唤醒的线程,就无序的竞争锁

如果对方没有线程wait,或者只有一个线程wait,但是另一个线程notify多次,会咋样呢?
不会咋样,notify通知的时候,如果无人wait,不会有任何副作用

import java.util.Scanner;

public class Demo25 {
    private static Object locker = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t1 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1 wait 之后");
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t2 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2 wait 之后");
            }
        });
        Thread t3 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t3 wait 之前");
                try {
                    locker.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3 wait 之后");
            }
        });
        Thread t4 = new Thread(() -> {
            Scanner sc = new Scanner(System.in);
            sc.next();
            System.out.println("t4 notify 之前");
            synchronized (locker) {
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
                locker.notify();
            }
            System.out.println("t4 notify 之后");
        });

        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}

wait 和 sleep 的对⽐(⾯试题)

wait默认也是"死等"
wait还提供带参数的版本,指定超时时间
如果wait达到了最大的时间,还没有notify就不会继续等待了,而是直接继续执行.
wait(1000)和sleep (1000)看起来就有点相似了~~
wait和sleep有本质区别的
使用wait的目的是为了提前唤醒。sleep就是固定时间的阻塞,不涉及到唤醒的
虽然sleep可以被Interrupt唤醒,Interrupt操作,表示的意思不是"唤醒"而是要终止线程了
wait必须要搭配synchronized使用,并且wait会先释放锁,同时进行等待
sleep和锁无关.如果不加锁,sleep可以正常使用,如果加了锁,sleep操作不会释放锁"抱着锁"一起睡,其他线程无法拿到锁的
总结下:

1. wait 需要搭配 synchronized 使⽤,sleep 不需要.

2. wait 是 Object 的⽅法 sleep 是 Thread 的静态⽅法.

练习

创建三个线程,分别打印A,B,C 通过wait、notify约定线程的打印顺序,先打印A,然后B,最后C

public class Demo26 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("A");
        });
        Thread t2 = new Thread(() -> {
            System.out.println("B");
        });
        Thread t3 = new Thread(() -> {
            System.out.println("C");
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

public class Demo26 {
    private static Object locker1 = new Object();

    private static Object locker2 = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("A");
            synchronized (locker1) {
                locker1.notify();
            }
        });
        Thread t2 = new Thread(() -> {
            synchronized (locker1) {
                try {
                    locker1.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("B");

            synchronized (locker2) {
                locker2.notify();
            }
        });
        Thread t3 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    locker2.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("C");
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

如果这个代码中,是先执行t2的wait,后执行t1的notify,代码的逻辑是一切顺利的
(大概率是这样的,因为t1的打印需要消耗不少时间)
但是实际上,存在这样的概率,t1先执行了打印和notify,然后t2才执行wait,意味着通知来的早了,t2错过了,t2的wait就无人唤醒了

只需要在t1中加上sleep

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/952749.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA的常用设置

目录 一、显示顶部工具栏 二、设置编辑区字体按住鼠标滚轮变大变小(看需要设置) 三、设置自动导包和优化导入的包(有的时候还是需要手动导包) 四、设置导入同一个包下的类,超过指定个数的时候,合并为*&a…

Xcode 正则表达式实现查找替换

在软件开发过程中,查找和替换文本是一项常见的任务。正则表达式(Regular Expressions)是一种强大的工具,可以帮助我们在复杂的文本中进行精确的匹配和替换。Xcode 作为一款流行的开发工具,提供了对正则表达式的支持。本…

UE材质函数

材质函数是可在不同材质中重复使用的材质表达式的一个集合 相当于把常用的功能封装到一个集合里,需要用到的时候调用 输入input可以添加输入节点 如果勾上公开到库,就可以在材质面板直接搜索到材质函数 材质函数可以直接做成一个输出

vue3后台系统动态路由实现

动态路由的流程:用户登录之后拿到用户信息和token,再去请求后端给的动态路由表,前端处理路由格式为vue路由格式。 1)拿到用户信息里面的角色之后再去请求路由表,返回的路由为tree格式 后端返回路由如下: …

【DAPM杂谈之二】实践是检验真理的标准

本文主要分析DAPM的设计与实现 内核的版本是:linux-5.15.164,下载链接:Linux内核下载 主要讲解有关于DAPM相关的知识,会给出一些例程并分析内核如何去实现的 /**************************************************************…

【Qt】事件、qt文件

目录 Qt事件 QEvent QMouseEvent QWheelEvent QKeyEvent QTimerEvent Qt文件 QFile QFileInfo Qt事件 在Qt中用一个对象表示一个事件,这些事件对象都继承自抽象类QEvent。事件和信号的目的是一样的,都是为了响应用户的操作。有两种产生事件的方…

线形回归与小批量梯度下降实例

1、准备数据集 import numpy as np import matplotlib.pyplot as pltfrom torch.utils.data import DataLoader from torch.utils.data import TensorDataset######################################################################### #################准备若干个随机的x和…

消息队列使用中防止消息丢失的实战指南

消息队列使用中防止消息丢失的实战指南 在分布式系统架构里,消息队列起着举足轻重的作用,它异步解耦各个业务模块,提升系统整体的吞吐量与响应速度。但消息丢失问题,犹如一颗不定时炸弹,随时可能破坏系统的数据一致性…

【优选算法篇】:深入浅出位运算--性能优化的利器

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨ ✨ 个人主页:余辉zmh–CSDN博客 ✨ 文章所属专栏:优选算法篇–CSDN博客 文章目录 一.位运算一.位运算概述二.常见的位运算操作符三.常见的位运…

创业AI Agents系统深度解析

Agents 近日,AI领域的知名公司Anthropic发布了一份题为《构建高效的智能代理》的报告。该报告基于Anthropic过去一年与多个团队合作构建大语言模型(LLM)智能代理系统的经验,为开发者及对该领域感兴趣的人士提供了宝贵的洞见。本文…

【Spring Boot】Spring 事务探秘:核心机制与应用场景解析

前言 🌟🌟本期讲解关于spring 事务介绍~~~ 🌈感兴趣的小伙伴看一看小编主页:GGBondlctrl-CSDN博客 🔥 你的点赞就是小编不断更新的最大动力 🎆那么废话不多说直…

centos7.6 安装nginx 1.21.3与配置ssl

1 安装依赖 yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel2 下载Nginx wget http://nginx.org/download/nginx-1.21.3.tar.gz3 安装目录 mkdir -p /data/apps/nginx4 安装 4.1 创建用户 创建用户nginx使用的nginx用户。 #添加www组 # groupa…

夯实前端基础之HTML篇

知识点概览 HTML部分 1. DOM和BOM有什么区别? DOM(Document Object Model) 当网页被加载时,浏览器会创建页面的对象文档模型,HTML DOM 模型被结构化为对象树 用途: 主要用于网页内容的动态修改和交互&…

Elasticsearch:向量数据库基础设施类别的兴衰

过去几年,我一直在观察嵌入技术如何从大型科技公司的 “秘密武器” 转变为日常开发人员工具。接下来发生的事情 —— 向量数据库淘金热、RAG 炒作周期以及最终的修正 —— 教会了我们关于新技术如何在更广泛的生态系统中找到一席之地的宝贵经验。 更多有关向量搜索…

【华为云开发者学堂】基于华为云 CodeArts CCE 开发微服务电商平台

实验目的 通过完成本实验,在 CodeArts 平台完成基于微服务的应用开发,构建和部署。 ● 理解微服务应用架构和微服务模块组件 ● 掌握 CCE 平台创建基于公共镜像的应用的操作 ● 掌握 CodeArts 平台编译构建微服务应用的操作 ● 掌握 CodeArts 平台部署微…

计科高可用服务器架构实训(防火墙、双机热备,VRRP、MSTP、DHCP、OSPF)

一、项目介绍 需求分析: (1)总部和分部要求网络拓扑简单,方便维护,网络有扩展和冗余性; (2)总部分财务部,人事部,工程部,技术部,提供…

【C++入门】详解合集

目录 💕1.C中main函数内部———变量的访问顺序 💕2.命名空间域 namespace 💕3.命名空间域(代码示例)(不要跳) 💕4.多个命名空间域的内部重名 💕5.命名空间域的展开 …

预编译SQL

预编译SQL 预编译SQL是指在数据库应用程序中,SQL语句在执行之前已经通过某种机制(如预编译器)进行了解析、优化和准备,使得实际执行时可以直接使用优化后的执行计划,而不需要每次都重新解析和编译。这么说可能有一些抽…

qemu搭建虚拟的aarch64环境开发ebpf

一、背景 需求在嵌入式环境下进行交叉编译,学习ebpf相关技术,所以想搭建一个不依赖硬件环境的学习环境。 本文使用的环境版本: 宿主机: Ubuntu24.02 libbpf-bootstrap源码: https://github.com/libbpf/libbpf-boots…

深度学习从入门到实战——卷积神经网络原理解析及其应用

卷积神经网络CNN 卷积神经网络前言卷积神经网络卷积的填充方式卷积原理展示卷积计算量公式卷积核输出的大小计算感受野池化自适应均值化空洞卷积经典卷积神经网络参考 卷积神经网络 前言 为什么要使用卷积神经网络呢? 首先传统的MLP的有什么问题呢? - …