Java 多线程(五)—— 阻塞队列、wait、notify

news2024/11/28 2:32:06

wait

wait 和 notify 都是 Object 类提供的方法,也就是说 Java 任意对象都可以使用 这两个方法。

在这里插入图片描述

首先 wait 会抛出 InterruptedException 这个异常,说明这个方法可以被 interrupt 给唤醒。

然后我们是不能直接使用 wait 方法的,否则还会抛出下面的异常:

在这里插入图片描述

IllegalMonitorStateException 是非法的锁状态,monitor 是监视器 也就是我们 的 synchronized ,后面 current thread is not owner ,是说当前的线程不是这把锁的拥有者,说明我们要想使用 wait 方法 就需要先获得这把锁

    public static void main(String[] args) throws InterruptedException {
        Object locker = new Object();
        synchronized(locker) {
            locker.wait();
        }
    }

wait 的作用是让当前的线程先释放这把锁,然后进入 waiting(死等) 状态,当然 wait 也和 sleep 一样也可以设置时间,当该线程在规定时间内没有被唤醒,就会自动唤醒,重新争夺锁。这时候有时间限制的 wait 的线程状态就是 time_waiting 状态。

我们来看一下wait 的流程图:
在这里插入图片描述

wait 和 sleep 的区别:
首先在 synchronized 下, wait 是会释放掉锁的,但是sleep 不会,sleep 会抱着锁一起睡
wait 的使用必须搭配锁, sleep 不需要


当我们使用 wait 的时候一般是搭配条件判断的,这里建议 将条件判断换成 while 循环语句,这也是Java 标准库里写明的:

在这里插入图片描述

翻译:等待的最佳方法是在等待调用周围的 while 循环中检查正在等待的条件,如下面的例子所示。这种方法可以避免由随机唤醒引起的问题。

这个方案其实是操作系统原生的 api 就建议过 wait 搭配 while,Java 只是继承前人的意志,当然具体你要使用 if 还是使用 while 还是要看具体问题具体分析的。

因为 wait 可能会被 interrupt() 给唤醒,如果使用 if 作为判断,此时就可能会存在 wait 被提前唤醒的 情况。

notify

notify 是唤醒线程,在操作系统原生的api 中 调用 notify 的线程是可以不用获得对应对象的锁的,但是在Java中规定了 notify 的使用和 wait 是一样都需要先获得这把锁对象

wait 和 notify 针对的是同一个对象,notify 才能生效, wait 的线程才能被唤醒

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

        Thread t2 = new Thread(() -> {
            synchronized (locker) {
                System.out.println("t2 开始唤醒 t1");
                locker.notify();
            }
        });
        t1.start();
        t2.start();
    }

在这里插入图片描述

一个 notify 只能唤醒一个 wait ,如果有多个 wait 的话,一个 notify 会随机唤醒其中一个 wait 线程
如果你想唤醒全部的 wait 线程,可以使用 notifyAll()

如果notify 在 wait 之前就已经运行了,那 后面的 wait 是不会接收到 前面的 notify 信号的,所以我们要确保 notify 在 wait 之后执行,避免程序卡住。


wait 和 notify 的使用不仅仅是为了线程等待和线程唤醒这些基本的操作,其实还有一个作用,就是让线程释放掉锁,也就是当线程此时不满足条件无法进行工作的时候,应该将锁资源释放掉,给其他线程用用,这也避免了线程饥饿问题(线程迟迟没能得到 CPU 资源而运行),进而提高资源的利用率

阻塞队列

阻塞队列是另一种设计模式,是为了实现生产者和消费者模型的。

在这里插入图片描述

生产者负责生产,生产出来的产品会被消费者进行消耗,而我们的阻塞队列其实就是连接生产者和消费者的管道,阻塞队列是由容量的,当生产者生产速度过快,消费者的消费速度跟不上生产的速度,阻塞队列在某一个时刻就会发生阻塞,即当丢列已满的情况下,生产者不能继续生产往队列里面填充产品。

同理,当消费者的消费速度过快,生产者跟不上其消费速度,阻塞队列就会在某一个时刻为空,此时消费者就不能继续从队列里获取产品了,即当丢列为空的时候,消费者会处于阻塞状态。

阻塞队列的特性
当队列为空,尝试进行出队列的操作,这时候进行出队列的操作的线程会发生阻塞,直到其他线程添加其他元素未知
当队列已满,尝试进行入队列的操作,这时候进行入队列操作的线程会发生阻塞,直到其他线程从队列取走元素为止


在实际开发过程中,我们如果要使用阻塞队列,很有可能会将一个阻塞队列放在一个服务器(机器 / 集群)上进行部署。

原先不使用阻塞队列的时候,A 服务器和 B 服务器直接进行交互,很有可能发生一件事情:当 A 服务器发出过量的请求的时候,B 服务器就可能无法处理这么多请求而发生崩溃,这个例子大家请参考学校的教务系统之选课环节。

在这里插入图片描述

当我们使用阻塞队列,并且把它部署到一个集群上时:
在这里插入图片描述
A 服务器和 B服务器就会与队列进行交互,从而实现 削峰填谷,即使A 服务器 有一大波请求涌来,这时候这一波数据并不会直接被 B 服务器接收,而是被 队列给拦下来了,B 服务器依旧可以按照自己的节奏从 队列里获取请求然后进行响应,这样 B 服务器就没这么容易崩溃。

不仅如此,还能实现 代码的 解耦合,这样的部署,就会使 A 和 B 的代码耦合性变低。

当然也有一个不好的地方,就是你多使用了一个机器来部署阻塞队列,这时候会增加机器的复杂性,生产环境会变得复杂,管理难度上升。并且引入队列,A 和 B服务器不是直接交互,效率也会受到影响

阻塞队列的优点:解耦合,削峰填谷
缺点:加机器的复杂性,生产环境会变得复杂,管理难度上升
效率受到影响

使用

在 Java 中给我们提供了一个阻塞队列的 类 BlockingQueue

在这里插入图片描述

我们可以从上面直到这是一个接口,继承 Queue,说明我们可以使用 queue 的方法,但是注意 这是一个阻塞队列,Java 给我们提供了带有阻塞功能的 方法 put() 和 take()


由于 BlockingQueue 本身是一个接口,所以无法直接实例化,Java 给我们提供了下面的实例化方式,我们可以创建基于数组实现的阻塞队列、基于链表实现的阻塞队列、也可以创建带有优先级的阻塞队列、还可以创建双端队列

在这里插入图片描述
在这里插入图片描述


当我们使用 put 和 take 方法的时候记得抛出 InterruptedException 异常

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10);
        queue.put(10);
        queue.take();
    }
}

模拟实现

现在我们来模拟实现一个基于数组实现的阻塞循环队列。

class MyBlockingQueue {
    private int[] elem;
    private int size;
    private int head;
    private int tail;

    Object locker = new Object();

    public MyBlockingQueue(int capacity) {
        elem = new int[capacity];
    }

    public void put(int x) throws InterruptedException {
        synchronized (locker) {
            while (size == elem.length) {
                locker.wait();
            }
            elem[tail] = x;
            tail++;
            if (tail == elem.length) {
                tail = 0;
            }
            size++;
            locker.notify();
        }
    }

    public int take() throws InterruptedException {
        synchronized (locker) {
            while (size == 0) {
                locker.wait();
            }
            int x = elem[head];
            head++;
            if (head == elem.length) {
                head = 0;
            }
            size--;
            locker.notify();
            return x;
        }
    }
}

这里着重介绍 wait 和 notify 的作用,当我们进行 put 操作的时候,如果发现队列已满,我们需要进入线程等待状态,等到 take 拿走元素之后就进行 notify 操作唤醒 put ;同理,当进行 take 操作的时候,如果发现队列为空,要进入等待状态,等到 put 放入元素之后进行 notify 操作唤醒 take
在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win10系统安装docker操作步骤

Docker下载 docker下载地址&#xff1a;Docker: Accelerated Container Application Development 打开网页后&#xff0c;点击图下所示&#xff0c;下载windows版本的docker 启用Hyper-V 和容器特性 右键左下角windows图标&#xff0c;选择应用和功能 然后在下面的界面中&am…

电脑技巧:Rufus——最佳USB启动盘制作工具指南

目录 一、功能强大&#xff0c;兼容性广泛 二、界面友好&#xff0c;操作简便 三、快速高效&#xff0c;高度可定制 四、安全可靠&#xff0c;社区活跃 在日常的电脑使用中&#xff0c;无论是为了安装操作系统、修复系统故障还是进行其他需要可引导媒体的任务&#xff0c;拥…

初始JavaEE篇——多线程(2):join的用法、线程安全问题

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 模拟实现线程中断 join的用法 线程的状态 NEW&#xff1a; RUNNABLE&#xff1a; TIMED_WAITING&#xff1a; TERMINATED…

ElasticSearch-7.17.10集群升级至ElasticSearch-7.17.24

文章目录 集群概览 主机名系统版本es01CentOS_7.6-aaarch64ElasticSearch-7.17.10es02CentOS_7.6-aaarch64ElasticSearch-7.17.10es03CentOS_7.6-aaarch64ElasticSearch-7.17.10 需求 1. 将三台ES节点从ElasticSearch-7.17.10升级至ElasticSearch-7.17.24&#xff1b; 2. 保证…

1212,查询球队积分

查询球队积分 表: Teams ------------------------- | Column Name | Type | ------------------------- | team_id | int | | team_name | varchar | ------------------------- team_id 是该表具有唯一值的列。 表中的每一行都代表一支独立足球队。表…

HarmonyOS 模块化设计

1.HarmonyOS 模块化设计 模块化设计文档   应用程序包开发与使用文档 1.1. 概述 组件化一直是移动端比较流行的开发方式&#xff0c;有着编译运行快&#xff0c;业务逻辑分明&#xff0c;任务划分清晰等优点&#xff0c;HarmonyOs组件化的使用&#xff0c;有利于模块之间的解…

【WRF数据准备】地形-SRTM的3s高分辨率地形数据集

【WRF数据准备】地形-SRTM的3s高分辨率地形数据集 数据概述数据下载 数据处理合并多个SRTM 数据-GDAL库转为geogrid二进制格式WPS 中的设置 数据对比海洋区域缺省值参考 WRF中地形数据&#xff08;海拔高度&#xff09;分辨率最高为30s&#xff0c;差不多就是900 m&#xff0c;…

CST光子晶体微谐振腔分析和Q值提取

本期介绍基于文献[1]的一种二维光子晶体波导结构&#xff0c;利用路径上加微谐振腔来实现一些特殊的滤波功能。一般是要看谐振频率的变化和Q值变化&#xff0c;因为工艺误差或任何造成结构不规则的因素对这样细小的结构谐振来说影响非常大。下图为文献中提到的硅薄膜结构&#…

使用Jenkins持续集成的一些经验总结!

01、Performance插件兼容性问题 自由风格项目中&#xff0c;有使用 Performance 插件收集构建产物&#xff0c;但是截至到目前最新版本&#xff08;Jenkins v2.298&#xff0c;Performance&#xff1a;v3.19&#xff09;&#xff0c;此插件和Jenkins都存在有兼容性问题&#x…

业余时间试一试利用AI 人工智能赚钱

内容创作与写作&#xff1a; 撰写文章&#xff1a;许多网站、博客和企业都需要大量的优质内容。利用 AI 工具如 ChatGPT 等&#xff0c;获取文章的思路、框架甚至初稿&#xff0c;然后根据自己的知识和经验进行修改、润色和完善。你可以在一些自由撰稿人平台、内容创作平台上承…

autumn是 “秋天”,year是 “年”,那autumn years是什么意思?柯桥商务剑桥英语学习外贸口语

autumn是“秋天”&#xff0c;year是“年”&#xff0c; 那你知道 autumn years 是什么意思&#xff1f; autumn years是什么意思&#xff1f; autumn years 直译为“秋天的15857575376*年”&#xff0c;但这样的理解并不准确&#xff0c;《剑桥辞典》中对这个词组的英文解释…

如何评估检索增强型生成(RAG)应用

RAG&#xff0c;也就是检索增强型生成&#xff0c;是现在大型语言模型&#xff08;LLMs&#xff09;时代里的一个超火的AI框架&#xff0c;比如你知道的ChatGPT。它通过把外面的知识整合进来&#xff0c;让这些模型变得更聪明&#xff0c;能给出更准确、更及时的回答。详见前篇…

[WiFi] Wi-Fi HaLow: IEEE 802.11ah 无线网络协议介绍

参考链接 802.11ah&#xff08;HaLow&#xff09;协议解析1&#xff1a;协议简介 - 知乎 802.11ah&#xff08;HaLow&#xff09;协议解析3&#xff1a;物理层改进 - 知乎 Wi-Fi HaLow: IEEE 802.11ah Wireless Networking Protocol - IoTEDU Wi-Fi CERTIFIED HaLow | Wi-F…

实现iOS Framework生成全流程详解

引言 在iOS开发中&#xff0c;Framework是实现代码复用和模块化开发的有效手段。它不仅可以将复杂的功能封装为独立的组件&#xff0c;还能提升代码的可维护性和可扩展性。Framework的广泛应用使得我们可以轻松地集成第三方库&#xff0c;或将自己的功能打包分发给团队成员使用…

CF351E Jeff and Permutation 题解

#1024程序员节&#xff5c;征文# 人生中的第一道紫题。。。 ​​​​​​题目传送门 解题思路 首先我们可以得到读入时 的正负不影响答案&#xff0c;因为我们可以进行一次操作将它们变成它们的相反数&#xff0c;从而使其变成原数&#xff0c;因此&#xff0c;我们可以将…

项目篇--Maven+Idea+ PrimeFaces+Jsf--项目搭建

文章目录 前言一、PrimeFaces 和 Jsf&#xff1a;1.1 JSF 基础&#xff1a;1.2 PrimeFaces 扩展&#xff1a; 二、项目搭建&#xff1a;2.1 Maven 项目的创建&#xff1a;2 xml 配置&#xff1a;2.1 pom.xml 配置2.2web.xml 配置&#xff1a; 2.3 代码&#xff1a;2.3.1 页面&a…

(六)STM32F407 cubemx MPU6050通讯硬件寄存器配置部分(2)

这篇文章主要是个人的学习经验&#xff0c;想分享出来供大家提供思路&#xff0c;如果其中有不足之处请批评指正哈。废话不多说直接开始主题&#xff0c;本人是基于STM32F407VET6芯片&#xff0c;但是意在你看懂这篇文章后&#xff0c;不管是F1,F4,H7等一系列MPU6050通讯硬件寄…

Redis学习笔记(六)--Redis底层数据结构之集合的实现原理

文章目录 一、两种实现的选择二、ziplist1、head2、entries3、end 三、listPack1、head2、entries3、end 四、skipList1、skipList原理2、存在的问题3、算法优化 五、quickList1、检索操作2、插入操作3、删除操作 六、key与value中元素的数量 本文参考&#xff1a; Redis学习汇…

从天边的北斗到身边的北斗 —— 探索北斗导航系统的非凡之旅

引言&#xff1a;穿越时空的导航奇迹 在浩瀚的夜空之中&#xff0c;北斗七星以其独特的排列&#xff0c;自古以来便是指引方向的天文坐标。而今&#xff0c;这份古老的智慧与现代科技完美融合&#xff0c;化作了覆盖全球的卫星导航系统——中国北斗。从遥远的星河到触手可及的…

不考虑光影、背景、装饰,你的可视化大屏摆脱不了平淡。

如果在可视化大屏的设计中不考虑光影、背景和装饰&#xff0c;确实难以摆脱平淡。光影效果可以为大屏增添立体感和层次感&#xff0c;吸引观众的注意力。 合适的背景能营造出特定的氛围&#xff0c;使数据展示更具情境感。而装饰元素则可以起到点缀和美化的作用&#xff0c;提…