JavaEE 第7节 线程饥饿及其解决办法

news2024/9/19 10:39:15

目录

一、什么是线程饥饿?

二、线程饥饿的解决办法

*wait()与notify()方法解决线程饥饿

1、wait(等待)

2、notify(通知)

1)notify

2)notifyAll

3)关于wait方法的一些补充

1、wait的方法的三个功能是原子性的:

2、sleep与wait方法的异同

相同点:

不同点:

        所属类:

        锁释放:

        使用场景:

        调用位置:


一、什么是线程饥饿?

线程饥饿Thread Starvation)指的是多线程中,某些线程无法获得相关资源或者执行机会(阻塞,BLOCKED)长时间如此对线程的推进和响应造成明显影响的现象

左边的蘑菇头和右边的小人都是线程,左边的蘑菇头因为在所中没有自己想要的资源所以从锁中出来,但是由于它的优先级比较高一从锁里出来,转头有跑进锁里面了,完全没有给右边的小进去的机会

当饥饿到一定程度,赋予线程的任务即使完成也不在具有实际意义的时候就说明这个线程被饿死了

二、线程饥饿的解决办法

线程饥饿原因:

线程产生饥饿,主要是因为系统对线程调度方式不够公平或者不合理导致的

想要解决饥饿问题,我们只需要通过一些手段,对线程的调度进行合理的干预即可

比如上图中的蘑菇头线程,如果自己目前拿不到想要的资源结果,那就先把锁让给别的线程,不要自己在锁这里反复横跳!等别的线程把资源变量改成蘑菇头想要的结果的时候,然后再让蘑菇头进来拿锁。

*wait()与notify()方法解决线程饥饿

这两个方法可以让多个线程按照某一个逻辑上的先后顺序执行,从而避免线程的饿死。

注意两个方法都必须使用锁对象调用!

1、wait(等待)

wait()方法有三个功能:

1)让当前线程释放持有的锁

2)让当前线程进入WAITING状态(或者TIMED_WAITING,取决于括号中是否含有时间参数,ms)。

3)检测其他线程是否调用了同对象的notify()方法,如果调用了notify()方法,那么唤醒正在睡眠的线程


注释:

这三个功能都已经集成到的wait方法上,且是原子性的!(最后一个小节讲解原因)

使用方式:

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(()->{
          synchronized (lock){
              try {
                  lock.wait();//进入等待状态(WAITING),必须用lock调用
              } catch (InterruptedException e) {//与sleep方法类似,需要try catch,
                  // 但是wait不能自动唤醒自己,只能考notify方法

                  e.printStackTrace();
              }
          }
       });
       thread1.start();
       thread1.join();
    }
}

上面这个程序会造成程序的卡死。

原因:

wait()没有时间参数,那么就必须等其他线程用notify()方法唤醒它,但是我没有写notify方法,所以thread1会一直处在WAITINGZ状态,但是主线程有用了join()方法,所以主线程一直在等thread1线程运行完毕,进而造成程序的卡死,注意这不是死锁!

如果不想死等,那么可以使用带时间参数的重载方法:

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(()->{
          synchronized (lock){
              try {
                  lock.wait(1000);//带参数,1秒
              } catch (InterruptedException e) {//与sleep方法类似,需要try catch
                  e.printStackTrace();
              }
          }
       });
       thread1.start();
       thread1.join();
    }
}

2、notify(通知)

1)notify

我们刚才看到了,仅仅用wait方法是不够的,需要与notify方法搭配。

notify方法可以把因为wait而睡眠的线程唤醒:

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
       Thread thread1=new Thread(()->{
          synchronized (lock){
              try {

                  System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
                  lock.wait();
                  System.out.println("thread1成功被唤醒");
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
       });
       Thread thread2=new Thread(()->{
          synchronized (lock){
              try {
                  Thread.sleep(1000);//确保thread1线程先执行,不然唤醒个寂寞
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              System.out.println("thread2即将使用norify方法唤醒thread1");
              lock.notify();//同样,必须用lock调用!!
              System.out.println("thread2已唤醒thread1");
          }
       });


       thread1.start();
       thread2.start();
       thread1.join();
       thread2.join();
    }
}

执行结果:

倘若同时有多个线程调用wait方法,那么notify方法会"随机"的唤醒一个线程(具体由操作系统决定):

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread1成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread2即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread2成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread3 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread3即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread3成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread4 = new Thread(() -> {
            synchronized (lock) {
                try {
                    Thread.sleep(1000);//确保thread1线程先执行,不然唤醒个寂寞
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("thread4即将使用norify方法唤醒thread1");
                lock.notify();
                System.out.println("thread2已唤醒thread1");
            }
        });


        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread1.join();
        thread2.join();
        thread3.join();
        thread4.join();
    }
}

执行结果只会唤醒一个线程:

注意:

多次notify不会有副作用,即使没有线程wait过:

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
    Thread t1=new Thread(()->{
        synchronized (lock){
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
            lock.notify();
        }
    });
    t1.start();
    t1.join();
    }
}

当然不建议这样做,谁会这么无聊🤪

2)notifyAll

如果想要全部唤醒,也有办法,那就是使用notifyAll方法:

public class Threads {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread1即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread1成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread2即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread2成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread3 = new Thread(() -> {
            synchronized (lock) {
                try {

                    System.out.println("thread3即将进入睡眠,等待其他线程用notify方法唤醒");
                    lock.wait();
                    System.out.println("thread3成功被唤醒");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread4 = new Thread(() -> {

            
                try {
                    Thread.sleep(1000);//确保其他线程先执行,不然唤醒个寂寞
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                    synchronized (lock){
                        System.out.println("即将唤醒所有线程");
                        lock.notifyAll();/**换成notifyAll方法,其他代码都没有变*/
                        System.out.println("thread2已唤醒所有线程");
                    }

        });

        thread1.start();
        thread2.start();
        thread3.start();
        thread4.start();
        thread1.join();
        thread2.join();
        thread3.join();
        thread4.join();
    }
}

其中一个执行结果:

注意:

1)由于thread1、thread2、thread3是同时被唤醒的。之后这3个线程会进入锁竞争只有一个线程可以拿到锁然后执行,因此thread1、thread2、thread3三个线程那个先执行完是不确定的。

2)notify方法不像wait方法,wait方法调用的时候会释放当前对象的锁,但是notify方法没有这样的功能!!!也就是说,只有调用notify的线程释放了锁,被notify唤醒的线程才有机会执行。(notifyAll与notify是一样的)


3)关于wait方法的一些补充

1、wait的方法的三个功能是原子性的:

1)让当前线程释放持有的锁

2)让当前线程进入WAITING状态(或者TIMED_WAITING,取决于括号中是否含有时间参数,ms)。

3)检测其他线程是否调用了同对象的notify()方法,如果调用了notify()方法,那么唤醒正在睡眠的线程。(InterruptedException也会提前唤醒wait过的线程

假设不满足原子性:

有t1和t2两个线程,t1线程调用wait方法,要等待t2线程用notify唤醒t1。

若t1在执行完第一个功能后,由于线程调度的原因,t1在没有进入睡眠的状态就提前释放了锁,给t2,那么会出现这个情况:

t2线程提前执行了notify方法,t2线程运行完后,锁就交给了t1,紧接着t1就开始进入睡眠状态,等待t2线程用notify唤醒t1?这显然不符合逻辑。


2、sleep与wait方法的异同

相同点:

1)都可以暂停线程执行

2)都会抛出InterruptedException,需要对异常进行处理

注意

调用了wait方法的线程如果捕获到InterruptedException,此线程就会终止

不同点:
        所属类:

                1)sleep是Thread类的静态方法。

                2)wait是Object类的示例方法,且必须由锁对象调用。

        锁释放:

                1)用睡眠不会释放锁,也就是抱着锁睡:

                2)wait会释放锁,然后再睡。

        使用场景:

                1)sleep用于暂停线程执行一段时间,用的比较广泛。

                2)wait一般用在线程之间的通信,等待某个条件成立,接收notify信号。

        调用位置:

                1)sleep可以在任意地方调用。

                2)wait必须在同步块中调用(synchronized)。

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

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

相关文章

力扣刷题-环形链表判断是否有环

🌈个人主页:羽晨同学 💫个人格言:“成为自己未来的主人~” 首先,我们先来看一下这段代码: /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ bool …

【RISC-V设计-10】- RISC-V处理器设计K0A之IDU

【RISC-V设计-10】- RISC-V处理器设计K0A之IDU 文章目录 【RISC-V设计-10】- RISC-V处理器设计K0A之IDU1.简介2.顶层设计3.端口说明4.代码设计5.总结 1.简介 指令译码单元(Instruction Decoder Unit,简称IDU)是CPU中的一个关键组件&#xff…

用Vue和Axios将数据库数据显示在前端页面

在本次实例中Vue只用在了前端部分&#xff0c;Axios用于向后端请求数据&#xff0c;我们这里要用到Ajax技术来访问后端数据。 HTML&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name&quo…

CTF-RCE

eval执行 ?cmdsystemctl("ls"); ?cmdsystemctl("ls /"); ?cmdsystemctl("cat /flag_27523); 命令注入 输入ip试试发先可以执行 127.0.0.1 查看一下看看有社么 127.0.0.1 | ls 试着看看php文件 127.0.0.1 | cat 297581345892.php 貌似这个文件有…

韩式告白土味情话-柯桥生活韩语学习零基础入门教学

你们韩国人别太会告白了&#xff01; 1、너 얼굴에 뭐가 조금 묻었어! 你的脸上有点5376东西&#xff01; 뭐가 조금 묻었1585757는데? 有点什么&#xff1f; 이쁨이 조금 묻었네. 有点漂亮。 2、돌잡이 때 뭐 잡았어요&#xff1f; 你抓周的时候抓了什么&#xff1f; 쌀 잡았…

打开一个页面,整个过程会使用哪些协议?

打开一个页面&#xff0c;整个过程会使用哪些协议? 网络通信模型可以用下图来简单表示&#xff0c;根据下面这个顺序&#xff0c;我们来说明&#xff0c;打开一个页面&#xff0c;整个过程会使用哪些协议? 首先&#xff0c;我们可以梳理一个简单的完整流程: 1.在浏览器中输…

Postman Pre-request Script

这个其实是普通的js脚本&#xff0c;有一些和postman的通信他也提供了一些快捷命令如下 postman常用参数使用 环境变量 //设置当前环境变量 pm.environment.set("key", "value"); //获取当前环境变量 pm.environment.get("key"); //清除当前…

软件测试面试题汇总,超详细整理。。。

测试技术面试题 1、什么是兼容性测试&#xff1f;兼容性测试侧重哪些方面&#xff1f; 参考答案&#xff1a; 兼容测试主要是检查软件在不同的硬件平台、软件平台上是否可以正常的运行&#xff0c;即是通常说的软件的可移植性。 兼容的类型&#xff0c;如果细分的话&#x…

【Windows】如何关闭Windows11安全中心中的“病毒和威胁保护”?

按下“win&#xff08;徽标键&#xff09;i”快捷键&#xff0c;选择隐私与安全性-Windows安全中心。 选择防火墙和网络保护-域保护。 将开关闭&#xff0c;专业网络和公用网络防火墙也同样关闭&#xff0c;如下图所示&#xff1a; 关闭防火墙后&#xff0c;左边菜单栏选…

函数的学习(三)

1.函数的声明和定义 在C语言中&#xff0c;函数的声明和定义是分开的。 函数的声明是指在程序中提前告诉编译器有一个函数存在&#xff0c;并且指定了函数的名称、参数类型和返回值类型。函数的声明一般放在头文件中&#xff0c;它的作用是告诉编译器有一个函数存在&#xff…

学生综合测评、学生测评管理系统的设计与实现

摘要 学生综合测评是学校必不可少的一个部分。在教学中&#xff0c;学生综合测评担负着最重要的角色。为满足如今日益复杂的管理需求&#xff0c;各类学生综合测评也在不断改进。本课题所设计的学生综合测评&#xff0c;使用JSP技术与MySql数据库进行开发&#xff0c;它的优点代…

快速上手的企业视频会议系统需要具备哪些能力

随着企业规模的扩张&#xff0c;行政会议的增多以及企业的复杂性和地域分散性导致的信息传递周期过长问题&#xff0c;对企业运营效率和成本产生了负面影响。为了解决这些问题&#xff0c;许多企业开始采用视频会议系统以实现即时沟通并降低差旅开支。视频会议系统在远程沟通方…

牛客JS题(二十九) Map保存节点

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; map的基本使用forEach的注意点 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charset"utf-8" /></head><body><p>1</p><p>2<…

24.8.9数据结构|链栈和队列

链栈 1、理解 实际上是一个仅在表头进行操作的单链表,头指针指向栈顶结点或头结点,以下恋栈均指带头结点的链栈. 2、 基本操作 1、定义结构&#xff1a;节点含有数据域和指针域 2、初始化操作&#xff1a;建立一个带头结点的空栈 3、取栈顶元素操作&#xff1a;取出栈的栈顶元…

【最新】推荐6款论文ai写论文软件推荐网站平台

在当前的学术研究和写作环境中&#xff0c;AI写作工具已经成为许多学者和学生的重要助手。这些工具不仅能够提高写作效率&#xff0c;还能帮助优化文章结构、润色语言以及进行查重等任务。以下将推荐六款优秀的AI写论文软件&#xff0c;并详细介绍它们的功能和特点。 1. 千笔-…

Spring及相关框架的重要的问题

Java框架 问题一&#xff1a;Spring框架中的单例bean是线程安全的吗&#xff1f; 看下图&#xff0c;不能被修改的成员变量就是无状态的类&#xff0c;无状态的类没有线程安全问题&#xff0c;所以在开发中尽量避免可修改的成员变量。 回答&#xff1a;不是线程安全的&#xf…

Drizzle ORM使用Azure PostgreSQL数据库迁移失败

执行 npx drizzle-kit migrate 命令报错&#xff1a; No config path provided, using default drizzle.config.ts Reading config file E:\nextjs-cloudflare-drizzle\drizzle.config.ts Using postgres driver for database querying [⡿] applying migrations...PostgresEr…

Intersection Observer API 帮你搞定前端滚动问题

前言 当我们在做需求时&#xff0c;可能经常会遇到很多跟页面的滚动有关的需求。例如 图片的懒加载&#xff1a;我们希望只加载用户当前视图窗口的图片&#xff0c;而未进入到视图窗口的图片&#xff0c;只有在进入到视图窗口时才进行加载&#xff0c;以提高页面响应速度&…

简单报表示例

简单报表 概述 简单表格报表&#xff1a;简单的表格报表。 应用场景 如下图所示&#xff0c;简单展示数据 示例说明 数据准备 在数据面板中添加数据集&#xff0c;可选择Json数据集和API服务数据集。Json数据集输入如下图所示&#xff1a; [{"到货日期":&qu…

工业智能网关在制造业数字化转型中的应用分享-天拓四方

随着工业4.0和物联网技术的飞速发展&#xff0c;工业智能网关已成为连接现场设备、实现数据采集与远程监控的关键节点。本文将以一个实际使用案例为蓝本&#xff0c;深入探讨工业智能网关的应用价值、技术特点以及实施效果&#xff0c;旨在为读者呈现一个清晰、专业且实操性强的…