等待唤醒机制和阻塞队列

news2024/9/23 23:25:55

在这里插入图片描述

 

1. 等待唤醒机制

由于线程的随机调度,可能会出现“线程饿死”的问题:也就是一个线程加锁执行,然后解锁,其他线程抢不到,一直是这个线程在重复操作

void wait()

当前线程等待,直到被其他线程唤醒

void notify()

随机唤醒单个线程

void notifyAll()

唤醒所有线程

等待(wait):当一个线程执行到某个对象的wait()方法时,它会释放当前持有的锁(如果有的话),并进入等待状态。此时,线程不再参与CPU的调度,直到其他线程调用同一对象的notify()或notifyAll()方法将其唤醒,类似的,wait() 方法也可以传入一个参数表示等待的时间,不加参数就会一直等

唤醒(notify/notifyAll):

notify: 唤醒在该对象监视器上等待的某个线程,如果有多个线程在等待,那么具体唤醒哪一个是随机的

notifyAll: 唤醒在该对象监视器上等待的所有线程

1.1. wait

上面的方法是Object提供的方法,所以任意的Object对象都可以调用,下面来演示一下:

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

结果抛出了一个异常:非法的锁状态异常,也就是调用wait的时候,当前锁的状态是非法的

这是因为,在wait方法中,会先解锁然后再等待,所以要使用wait,就要先加个锁,阻塞等待就是把自己的锁释放掉再等待,不然一直拿着锁等待,其他线程就没机会了


把wait操作写在synchronized方法里就可以了,运行之后main线程就一直等待中,在jconsole中看到的也是waiting的状态

注意:wait操作进行解锁和阻塞等待是同时执行的(打包原子),如果不是同时执行就可能刚解锁就被其他线程抢占了,然后进行了唤醒操作,这时原来的线程再去等待,已经错过了唤醒操作,就会一直等

wait执行的操作:1. 释放锁并进入阻塞等待,准备接收唤醒通知 2. 收到通知后唤醒,并重新尝试获得锁

1.2. notify

接下来再看一下notify方法:

public class ThreadDemo15 {
    private static Object lock = new Object();
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            synchronized (lock){
                System.out.println("t1 wait 前");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("t1 wait 后");
            }
        });
        Thread t2 = new Thread(()->{
            synchronized (lock){
                System.out.println("t2 notify 前");
                Scanner sc = new Scanner(System.in);
                sc.next();//这里的输入主要是构造阻塞
                lock.notify();
                System.out.println("t2 notify 后");
            }
        });
    }
}

然后就会发现又出错了,还是之前的错误,notify也需要先加锁才可以

把之前的notify也加进synchornized就可以了,并且还需要确保是同一把锁

调用wait方法的线程会释放其持有的锁,被唤醒的线程在执行之前,必须重新获取被释放的锁

public class Cook extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 0) {
                        try {
                            Desk.lock.wait();//厨师等待
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        Desk.count--;
                        System.out.println("还能再吃" + Desk.count + "碗");
                        Desk.lock.notifyAll();//唤醒所有线程
                        Desk.foodFlag = 0;
                    }
                }
            }
        }
    }
}
public class Foodie extends Thread {
    @Override
    public void run() {
        while (true) {
            synchronized (Desk.lock) {
                if (Desk.count == 0) {
                    break;
                } else {
                    if (Desk.foodFlag == 1) {
                        try {
                            Desk.lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    } else {
                        System.out.println("已经做好了");
                        Desk.foodFlag = 1;
                        Desk.lock.notifyAll();
                    }
                }
            }
        }
    }
}
public class Desk {
    public static int foodFlag = 0;

    public static int count = 10;
    //锁对象
    public static Object lock = new Object();
}

这里实现的功能就是,厨师做好食物放在桌子上,美食家开始品尝,如果桌子上没有食物,美食家就等待,有的话,厨师进行等待

sleep() 和 wait() 的区别:

这两个方法看起来都是让线程等待,但是是有本质区别的,使用wait的目的是为了提前唤醒,sleep就是固定时间的阻塞,不涉及唤醒,虽然之前说的Interrupt可以使sleep提前醒来,但是Interrupt是终止线程,并不是唤醒,wait必须和锁一起使用,wait会先释放锁再等待,sleep和锁无关,不加锁sleep可以正常使用,加上锁sleep不会释放锁,抱着锁一起睡,其他线程无法拿到锁

在刚开始提到过,如果有多个线程都在同一个对象上wait,那么唤醒哪一个线程是随机的:

public class ThreadDemo16 {
    private static Object lock = new Object();

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

这次只是t1被唤醒了

还可以使用notifyAll,把全部的线程都唤醒

2. 阻塞队列

2.1. 阻塞队列的使用

阻塞队列是一种特殊的队列,相比于普通的队列,它支持两个额外的操作:当队列为空时,获取元素的操作会被阻塞,直到队列中有元素可用;当队列已满时,插入元素的操作会被阻塞,直到队列中有空间可以插入新元素。

当阻塞队列满的时候,线程就会进入阻塞状态:

public class ThreadDemo19 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(3);
        blockingDeque.put(1);
        System.out.println("添加成功");
        blockingDeque.put(2);
        System.out.println("添加成功");
        blockingDeque.put(3);
        System.out.println("添加成功");
        blockingDeque.put(4);
        System.out.println("添加成功");
    }
}

同时,当阻塞队列中没有元素时,再想要往外出队,线程也会进入阻塞状态

public class ThreadDemo20 {
    public static void main(String[] args) throws InterruptedException {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(20);
        blockingDeque.put(1);
        System.out.println("添加成功");
        blockingDeque.put(2);
        System.out.println("添加成功");
        blockingDeque.take();
        System.out.println("take成功");
        blockingDeque.take();
        System.out.println("take成功");
        blockingDeque.take();
        System.out.println("take成功");
    }
}

2.2. 实现阻塞队列

根据阻塞队列的特性,可以尝试来自己手动实现一下

可以采用数组来模拟实现:

public class MyBlockingDeque {
    private String[] data = null;
    private int head = 0;
    private int tail = 0;
    private int size = 0;

    public MyBlockingDeque(int capacity) {
        data = new String[capacity];
    }
}

接下来是入队列的操作:

public void put(String s) throws InterruptedException {
    synchronized (this) {
        while (size == data.length) {
            this.wait();
        }
        data[tail] = s;
        tail++;
        if (tail >= data.length) {
            tail = 0;
        }
        size++;
        this.notify();
    }
}

由于设计到变量的修改,所以要加上锁,这里调用wait和notify来模拟阻塞场景,并且需要注意wait要使用while循环,如果说被Interrupted打断了,那么就会出现不可预料的错误

出队列也是相同的道理:

public String take() throws InterruptedException {
String ret = "";
synchronized (this) {
    while (size == 0) {
        this.wait();
    }
    ret = data[head];
    head++;
    if (head >= data.length) {
        head = 0;
    }
    size--;
    this.notify();
}
return ret;
}

3. 生产者消费者模型

生产者消费者模型是一种经典的多线程同步模型,用于解决生产者和消费者之间的协作问题。在这个模型中,生产者负责生产数据并将其放入缓冲区,消费者负责从缓冲区中取出数据并进行处理。生产者和消费者之间通过缓冲区进行通信,彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度,提高系统的可维护性和可扩展性。

而阻塞队列可以当做上面的缓冲区:

public class ThreadDemo21 {
    public static void main(String[] args) {
        BlockingDeque<Integer> blockingDeque = new LinkedBlockingDeque<>(100);
        Thread t1 = new Thread(()->{
            int i = 1;
            while (true){
                try {
                    blockingDeque.put(i);
                    System.out.println("生产元素:" + i);
                    i++;
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        Thread t2 = new Thread(()->{
            while (true){
                try {
                    int i = blockingDeque.take();
                    System.out.println("消费元素:" + i);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t1.start();
        t2.start();
    }
}

如果说把sleep的操作放到线程2会怎么样?

线程一瞬间就把阻塞队列沾满了,后面还是一个线程生产,一个线程消费,虽然打印出来的有偏差

生产者和消费者之间通过缓冲区进行通信,彼此之间不需要直接交互。这样可以降低生产者和消费者之间的耦合度,提高系统的可维护性和可扩展性。 

在这里插入图片描述

 

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

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

相关文章

【QT】自制一个简单的时钟(跟随系统时间)

目录 源代码&#xff1a; 输出结果如下&#xff1a; 使用QT完成一个简单的时钟图形化界面&#xff0c;功能是完成了时分秒指针能够跟随系统时间移动 设计思路&#xff1a; 1、首先将时钟的边框绘制出来 2、定义出一个定时器t1&#xff0c;将定时器连接到update_slot槽内&#…

CSS 常用元素属性

CSS 属性有很多, 可以参考文档 CSS 参考手册 1. 字体属性 设置字体 多个字体之间使用逗号分隔. (从左到右查找字体, 如果都找不到, 会使用默认字体. )如果字体名有空格, 使用引号包裹.建议使用常见字体, 否则兼容性不好. <style>.one {font-family:"Microsoft Ya…

Docker数据卷介绍及相关操作

数据卷的介绍 数据卷&#xff08;Data Volumes&#xff09;&#xff1a;是一个虚拟目录&#xff0c;是容器内目录与宿主机目录之间映射的桥梁。 对数据卷的修改会立马生效数据卷可以在容器之间共享和重用对数据卷的更新&#xff0c;不会影响镜像数据卷默认会一直存在&#xf…

Element UI:初步探索 Vue.js 的高效 UI 框架

Element UI&#xff1a;初步探索 Vue.js 的高效 UI 框架 一 . ElementUI 基本使用1.1 Element 介绍1.2 Element 快速入门1.3 基础布局1.4 容器布局1.5 表单组件1.6 表格组件1.6.1 基础表格1.6.2 带斑马纹表格1.6.3 带边框表格1.6.4 带状态的表格 1.7 导航栏组件讲解 二 . 学生列…

动态规划(一)——斐波那契数列模型

文章目录 斐波那契数列模型第N个泰波那契数 补充&#xff1a;空间优化——滚动数组三步问题最小花费爬楼梯解码方法 斐波那契数列模型 回头总结&#xff1a; 斐波那契数列模型一般都是线性dp&#xff0c;对于这类DP题目的状态表示一般是 以i为结尾&#xff0c;… 分析状态转移方…

google vr 入门之VrPanoramaView制作全景图列表(1)

展示图片的列表我这里使用RecycleView&#xff0c;activity_main.xml <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android“http://schemas.android.com/apk/res/android” xmlns:tools“http://schemas.android.com/tool…

又一个iPhone时代开始

今年的苹果秋季发布会在昨晚召开了&#xff0c;今天早上我们也看到了很多相关的新闻。我猜你看完后的感觉可能是&#xff0c;这不过又是一次普普通通的参数升级。又是提升了百分之多少&#xff0c;又是增加了多少倍——非常简单的一些更新。比如说芯片升级了、相机的摄像头一会…

【机器学习】7 ——k近邻算法

机器学习7——k近邻 输入&#xff1a;实例的特征向量 输出&#xff1a;类别 懒惰学习&#xff08;lazy learning&#xff09;的代表算法 文章目录 机器学习7——k近邻1.k近邻2.模型——距离&#xff0c;k&#xff0c;分类规则2.1距离——相似程度的反映2.2 k值分类规则 算法实…

Datawhale X 李宏毅苹果书 AI夏令营 《深度学习详解》第十九章 ChatGPT

19.1 ChatGPT 简介和功能 1、对话框可以输入任何东西 2、可以继续追问 19.2 对于 ChatGPT 的误解 1、第一个误解是 ChatGPT 的回答是罐头讯息 2、另外一个常见的误解是 ChatGPT 的答案是网络搜索的结果 3、那 ChatGPT 真正在做的事情是什么呢&#xff1f;一言以蔽之就是做…

【F179】基于Springboot+vue实现的幼儿园管理系统

作者主页&#xff1a;Java码库 主营内容&#xff1a;SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。 收藏点赞不迷路 关注作者有好处 文末获取源码 项目描述 系统管理也都将通过计算机进行整体智能化操作&#xff…

Adobe Acrobat DC无法将图片转换成PDF?教你用Python快速解决,最后附上集成小程序!

存在问题 当用Adobe Acrobat DC想将图片转换成PDF的时候&#xff0c;有时候会报错&#xff0c;如下&#xff1a; 多次尝试还是出现这个问题。 解决方案 基于Python代码实现 from PIL import Image import osdef images_to_pdf(input_folder, output_pdf):""&quo…

Emlog程序屏蔽用户IP拉黑名单插件

插件介绍 在很多时候我们需要得到用户的真实IP地址&#xff0c;例如&#xff0c;日志记录&#xff0c;地理定位&#xff0c;将用户信息&#xff0c;网站数据分析等,其实获取IP地址很简单&#xff0c;感兴趣的可以参考一下。 今天给大家带来舍力写的emlog插件&#xff1a;屏蔽…

【办公类】大组工会学习(文心一言+Python批量)

背景需求&#xff1a; 每学期要写一份工会的大组政治学习读后感&#xff08;9月-1月&#xff0c;共5次&#xff09; 学习内容 9月、10月、11月、12月、1月的学习内容文字稿 在班级里&#xff0c;我擅长电脑工作&#xff0c;所以这种写的工作都包了。 中2班三位老师一共写3篇&…

社区版IDEA连接MySQL数据库以及使用的详细方法

1、下载插件 由于社区版没有为我们提供DataBase&#xff0c;所有需要我们自己去下载DataBase插件。 步骤如下&#xff1a;1、File->Settings &#xff08;图片序号标错&#xff09;2、Plugins->搜索DataBase Navigator&#xff0c;点击install安装&#xff0c;安装…

新火种AI|估值飙升到千亿美元!OpenAI拿什么去支撑这惊人身价?

作者&#xff1a;小岩 编辑&#xff1a;彩云 OpenAI又有大动作了。 近期&#xff0c;有消息曝出&#xff0c;OpenAI正在进行新一轮的融资。此次融资阵仗极大&#xff0c;OpenAI很可能在本轮融资后估值飙升至千亿美元&#xff0c;成为全球范围内的“超级巨头”。 千亿估值的…

【机器学习-监督学习】集成学习与梯度提升决策树

【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈Python机器学习 ⌋ ⌋ ⌋ 机器学习是一门人工智能的分支学科&#xff0c;通过算法和模型让计算机从数据中学习&#xff0c;进行模型训练和优化&#xff0c;做出预测、分类和决策支持。Python成为机器学习的首选语言&#xff0c;…

erlang学习: Mnesia Erlang数据库3

Mnesia数据库删除实现和事务处理 -module(test_mnesia). -include_lib("stdlib/include/qlc.hrl").-record(shop, {item, quantity, cost}). %% API -export([insert/3, select/0, select/1, delete/1, transaction/1,start/0, do_this_once/0]). start() ->mnes…

[SAP ABAP] 清空ABAP变量

使用关键字CLEAR将变量中的值设置为默认值 代码结果如下所示

Win10磁盘出现小锁和感叹号的解决办法

很多说在设置-系统安全&#xff0c;但是我的么有&#xff0c;只能上命令了&#xff0c;管理员身份运行powerShell: su 速度比较慢&#xff0c;耐心等待会&#xff0c;每次查看状态加密的百分比都是减少哦 manage-bde -off G: manage-bde -status

JS获取URL参数的几种方法

JS获取URL参数的几种方法 在Web开发中&#xff0c;经常需要从URL中提取参数来进行相应的操作。本文将深度解析在JavaScript中获取URL参数的几种方法&#xff0c;并附带一些扩展与高级技巧。希望对你有所帮助&#xff01; 一、JS获取URL参数包含哪些方式 1. 使用URL对象 现代浏览…