线程的常用方法-wait和notify以及线程的结束方式

news2024/11/28 16:36:18

再复习一下Java中的线程的状态图

wait和sleep的区别是:wait需要先持有锁(wait需要再synchronized代码块中执行),执行后会让出锁。而sleep不需要先持有锁,执行后也不会释放锁(有锁的话抱着锁睡觉),执行wait之后被notify之前线程的状态是WAITING,而sleep之后的状态是TIMED_WAITING. 

来自互联网的解释:

wait和sleep的区别?

* 单词不一样。
* sleep属于Thread类中的static方法、wait属于Object类的方法
* sleep属于TIMED_WAITING,自动被唤醒、wait属于WAITING,需要手动唤醒。
* sleep方法在持有锁时,执行,不会释放锁资源、wait在执行后,会释放锁资源。
* sleep可以在持有锁或者不持有锁时,执行。 wait方法必须在只有锁时才可以执行。

wait方法会将持有锁的线程从owner扔到WaitSet集合中,这个操作是在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。

wait和notify

锁池中的状态是BLOCKED(有别的线程释放了锁资源,就会尝试竞争), 等待池中线程的状态是WAITING(需要别人使用notify唤醒),wait之后如果没有其他线程在锁池则线程会一直死等,像下面这样,永远不会结束

四个线程竞争锁

A拿到锁,其他的线程进入锁池

此时如果A执行了wait,则自己进入等待池,其他在锁池中的线程会继续竞争锁资源,最后肯定是一个线程能拿到锁

我们假设CD依次拿到锁并执行wait,则最后ACD3个线程都会出现在等待池中,此时B持有锁

如果此时B执行了notify则随机唤醒等待池中的3个线程中的一个,如果使用的是notifyAll则会把所有的等待池中的线程挪到锁池中准备竞争锁资源

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        sync();
    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

执行结果:

t1:0
t1:1
t1:2
t1:3
t1:4
main:0
main:1
main:2
main:3
main:4
t2:0
t2:1
t2:2
t2:3
t2:4

下面我们尝试从main线程把等待池中的线程唤醒,改完之后的代码如下:

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        Thread.sleep(10000);
        //尝试通过主线程随机唤醒一个等待池中的线程
        WaitAndNotify.class.notify();
    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

执行结果报错:

t1:0
t1:1
t1:2
t1:3
t1:4
t2:0
t2:1
t2:2
t2:3
t2:4
Exception in thread "main" java.lang.IllegalMonitorStateException
	at java.lang.Object.notify(Native Method)
	at thread.WaitAndNotify.main(WaitAndNotify.java:25)

这里是wait和notify、notifyAll要注意的点:必须持有锁才能执行者三个方法,实际表现为这三个方法要放在synchronized包住的块中执行,改造之后的方法如下:

package thread;

public class WaitAndNotify {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        Thread t2 = new Thread(()->{
            try {
                sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2");
        //sync并没有notify或者notifyAll,则线程会一直停着,什么也不执行
        t1.start();
        t2.start();
        //下面是main线程的执行过程,main现成执行sync同样会出现wait后线程不做任何事情的情况
        Thread.sleep(100);
        Thread.sleep(10000);
        //尝试通过主线程随机唤醒一个等待池中的线程
        synchronized (WaitAndNotify.class) {
            WaitAndNotify.class.notify();
        }

    }

    public static synchronized void sync() throws InterruptedException {
        for(int i = 0; i < 10; i++) {
            if(i == 5) {
                //这个需要考虑后序叫醒的过程,
                //如果所有的线程都wait了,并且wait的时候也没有执行notify和notifyAll,则线程会一直停着不执行任何操作
                WaitAndNotify.class.wait();
            }
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + ":" + i);
        }
    }
}

随机唤醒一个线程,我执行的时候唤醒的是t1,这个过程是随机的

t1:0
t1:1
t1:2
t1:3
t1:4
t2:0
t2:1
t2:2
t2:3
t2:4
t1:5
t1:6
t1:7
t1:8
t1:9

线程的结束方式

(1)stop方法

package thread;

public class TestStop {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();
        //主线程睡眠保证t1跑起来
        Thread.sleep(500);
        //获取t1目前的状态,应该是TIMED_WAITING
        System.out.println(t1.getState());
        //使用stop把t1强行终止掉
        t1.stop();
        //这里要睡眠一会,不然拿到的状态会是TIMED_WAITING,原因是在你调用t1.stop()后,线程调度器可能还没有来得及更新t1的状态。
        Thread.sleep(500);
        //获取t1现在的状态,应该是TERMINATED
        System.out.println(t1.getState());
    }
}

这里非常不推荐这种方式,真实的项目里很少会这么用,另外这个方法也已经过期了 

(2)使用共享变量

  也不常用

package thread;


public class TestStopByVariable {
    //这里一定要加volitile,t1线程无法感知到变化
    static volatile boolean flag = true;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
           while(flag) {

           }
            System.out.println("任务结束");
        });
        t1.start();
        Thread.sleep(500);
        flag = false;
    }
}

(3)使用interrupt方式

这种停止线程方式是最常用的一种,在框架和JUC中也是最常见的

interrupt的基本使用

package thread;

public class TestInterrupt {
    public static void main(String[] args) throws InterruptedException {
        //默认情况下interrupt标志位的值为false
        System.out.println(Thread.currentThread().isInterrupted());
        //对线程进行打断
        Thread.currentThread().interrupt();
        //再次查看标志位,
        System.out.println(Thread.currentThread().isInterrupted());
        //这个查看interrupt的方法同时会把interrupt恢复为默认值false
        System.out.println(Thread.interrupted());

        System.out.println(Thread.interrupted());
        Thread t1 = new Thread(()->{
            //获取标志位的状态并在被打断的时候终止循环
            //这个操作和我们使用变量进行停止没有区别
           while(!Thread.currentThread().isInterrupted()) {

           }
            System.out.println("t1结束");
        });
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
    }
}

最常用的使用interrupt方式结束线程的方式

package thread;

public class TestInterrupt2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
           while(true) {
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
                   System.out.println("基于打断的形式结束了当前线程");
                   return;
               }
           }
        });
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
    }
}

执行结果如下:

java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at thread.TestInterrupt2.lambda$main$0(TestInterrupt2.java:8)
    at java.lang.Thread.run(Thread.java:750)
基于打断的形式结束了当前线程

上面的异常是我们printStackTrace打印出来的,如果不需要刻意注释掉

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

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

相关文章

2024年天津天狮学院食品质量与安全专业《普通化学》考试大纲

2024年天津天狮学院食品质量与安全专业高职升本入学考试《普通化学》考试大纲 一、考试性质 《普通化学》专业课程考试是天津天狮学院食品质量与安全专业高职升本入学考试 的必考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《…

这一款 Mac 系统终端工具,已经用的爱不释手了!

&#x1f525;&#x1f525;&#x1f525;作为程序员或者运维管理人员&#xff0c;我们经常需要使用终端工具来进行服务器管理及各种操作&#xff0c;比如部署项目、调试代码、查看/优化服务、管理服务器等。 相信大家用的最多的终端工具就是 Xshell、iTerm2和Mobaxterm&#…

Vue框架学习笔记——计算属性

文章目录 前文提要代码需求描述插值语法实现methods实现 计算属性getter执行时间&#xff1a;setter 计算属性简写形式&#xff08;只读不改&#xff0c;才能如此简写&#xff09;slice截取元素&#xff0c;限制输入字符数量 前文提要 本人仅做个人学习记录&#xff0c;如有错…

vue3(二)-基础入门

一、列表渲染 of 和 in 都是一样的效果 html代码&#xff1a; <div id"app"><ul><li v-for"item of datalist">{{ item }}</li></ul><ul><li v-for"item in dataobj">{{ item }}</li></u…

聚观早报 |红魔9 Pro支持165W快充;2023Q3欧洲手机市场报告

【聚观365】11月22日消息 红魔9 Pro支持165W快充 2023Q3欧洲手机市场报告 长安汽车官宣加入蔚来换电体系 网龙战略投资Rokid IBM推出5亿美元的风险基金 红魔9 Pro支持165W快充 截至目前&#xff0c;除了已经发布或即将亮相的几款骁龙8 Gen3常规旗舰外&#xff0c;游戏手机…

梯度详解与优化实战

什么是梯度 对所有自变量求偏微分构成的向量&#xff0c;它是一个向量&#xff08;有大小和函数值增长方向&#xff09; 导数是一个标量 找最小值点坐标的案例 import torchimport numpy as np import matplotlib.pyplot as plt def himmelblau(x):return (x[0]**2x[1]-11)…

MFC 绘制单一颜色三角形、渐变颜色边框三角形、渐变填充三角形、正弦函数曲线实例

MFC 绘制三种不同圆形以及绘制正弦函数曲线 本文使用visual Studio MFC 平台实现绘制单一颜色圆形、渐变颜色边框圆形、渐变填充圆形以及绘制三角函数正弦函数曲线. 关于基础工程的创建请参考 01-Visual Studio 使用MFC 单文档工程绘制单一颜色直线和绘制渐变颜色的直线 02-vis…

SpringBoot 环境使用 Redis + AOP + 自定义注解实现接口幂等性

目录 一、前言二、主流实现方案介绍2.1、前端按钮做加载状态限制&#xff08;必备&#xff09;2.2、客户端使用唯一标识符2.3、服务端通过检测请求参数进行幂等校验&#xff08;本文使用&#xff09; 三、代码实现3.1、POM3.2、application.yml3.3、Redis配置类3.4、自定义注解…

部署项目时常用的 Linux 命令

目录 1 前言2 SSH登录命令3 SCP传输命令4 CP拷贝命令5 MV移动命令6 TAR解压命令7 DU查看文件夹/文件大小8 TAIL查看日志9 NOHUP后台运行10 结语 1 前言 在应用部署过程中&#xff0c;Linux命令是必不可少的工具。它们能够帮助我们管理文件、连接服务器、拷贝文件、查看日志以及…

大数据项目--学习笔记

新零售项目介绍 1&#xff0c;行业背景介绍 一&#xff0c;百货商店 百货商店是世界商业史上第一个实行新销售方法的现代大量销售组织。其新型销售方法有&#xff1a; 1&#xff0e;顾客可以毫无顾忌地、自由自在地进出商店&#xff1b; 2&#xff0e;商品销售实行“明码标价…

java基础语法总结

导言&#xff1a; Java语言是一种面向对象的编程语言&#xff0c;具有简单、可移植、安全、高性能等特点。本篇文章主要对java的基础的语法进行一个简单的总结和概述。 目录 导言&#xff1a; 正文&#xff1a; 1. 数据类型与变量 2. 运算符与逻辑控制 3. 方法 4. 数组…

数据结构 / 结构体字节计算

1. 结构体的存储 结构体各个成员的地址是连续的结构体变量的地址是第一个成员的地址 2. 64位操作系统8字节对齐 结构体的总字节大小是各个成员字节的总和&#xff0c;字节的总和需要是最宽成员的倍数结构体的首地址是最宽成员的倍数结构体各个成员的偏移量是该成员字节的倍数…

服务号和订阅号哪个好

服务号和订阅号有什么区别&#xff1f;服务号转为订阅号有哪些作用&#xff1f;在推送频率上来看&#xff0c;服务号每月能推送四条消息&#xff0c;而订阅号可以每天&#xff08;24小时&#xff09;推送一条消息。如果企业开通公众号的目的是提供服务&#xff0c;例如售前资讯…

JOSEF约瑟 BLD-20高压漏电保护继电器 50-1000ma AC220V

系列型号 BLD-20A高压漏电保护继电器 BLD-20高压漏电继电器 BLD-20高压漏电保护继电器 BLD-20X高压漏电保护装置 BLD-G20X高压漏电保护装置 用途 BLD-20高压漏电保护装置 (以下简称继电器)主用于交流电压1-10KV系统中,频率为50HZ,对供电系统的漏电(或接地)实现有选择性保…

MyBatis 操作数据库(入门)

一&#xff1a;MyBatis概念 (1)MyBatis &#x1f497;MyBatis是一款优秀的持久层框架&#xff0c;用于简化JDBC的开发 (2)持久层 1.持久层 &#x1f49c;持久层&#xff1a;持久化操作的层&#xff0c;通常指数据访问层(dao)&#xff0c;是用来操作数据库的 2.持久层的规范 ①…

【Web】Ctfshow Thinkphp3.2.3代码审计(1)

目录 ①web569 ②web570 ③web571 ④web572 ①web569 基础考察 /index.php/Admin/Login/ctfshowLogin ②web570 提示找路由 查看附件源码 (config.php) 发现定义了一个可执行命令的路由规则 /index.php/ctfshow/assert/eval($_POST[1]) 1system(tac /f*); ③web571 提…

The module to import is incompatible with the current project【鸿蒙开发-BUG已解决】

文章目录 项目场景:问题描述原因分析:解决方案:心得体会:知识点OpenHarmony:HarmonyOS:项目场景: 报错: The module to import is incompatible with the current project 问题描述 希望通过 import module 将该模块引入到我的项目。 导入后出现错误,因为项目和模块…

Scannet v2 数据集介绍以及子集下载展示

Scannet v2 数据集介绍以及子集下载展示 文章目录 Scannet v2 数据集介绍以及子集下载展示参考数据集简介子集scannet_frames_25kscannet_frames_test 下载脚本 download_scannetv2.py 参考 scannet数据集简介和下载-CSDN博客 scannet v2 数据集下载_scannetv2数据集_蓝羽飞鸟的…

pandas分组选中最大值并且新增列

题目 根据每个session_id分组&#xff0c;将popular最大的值设为这个session中所有popular的值 category item_id label popular session_id 0 4729 True 53.0 4069 0 4729 True 53.0 4069 0 4729 True 53.0 4069 0…

C++ PCL点云dscan密度分割三维

程序示例精选 C PCL点云dscan密度分割三维 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C PCL点云dscan密度分割三维》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。…