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

再复习一下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打印出来的,如果不需要刻意注释掉

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

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

相关文章

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

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

基于Haclon的标签旋转项目案例

项目要求&#xff1a; 图为HALCON附图“25interleaved_exposure_04”&#xff0c;里面为旋转的二维码标签&#xff0c;请将其旋转到水平位置。 项目知识&#xff1a; 在HALCON中进行图像平移和旋转通常有以下步骤&#xff1a; &#xff08;1&#xff09;通过hom_mat2d_ident…

jQuery_03 dom对象和jQuery对象的互相转换

dom对象和jQuery对象 dom对象 jQuery对象 在一个文件中同时存在两种对象 dom对象: 通过js中的document对象获取的对象 或者创建的对象 jQuery对象: 通过jQuery中的函数获取的对象。 为什么使用dom或jQuery对象呢&#xff1f; 目的是 要使用dom对象的函数或者属性 以及呢 要…

<JavaEE> 线程的五种创建方法 和 查看线程的两种方式

目录 一、线程的创建方法 1.1 继承 Thread -> 重写 run 方法 1.2 使用匿名内部类 -> 继承 Thread -> 重写 run 方法 1.3 实现 Runnable 接口 -> 重写 run 方法 1.4 使用匿名内部类 -> 实现 Runnable 接口 -> 重写 run 方法 1.5 使用 lambda 表达式 二…

Self Distillation 自蒸馏论文解读

paper&#xff1a;Be Your Own Teacher: Improve the Performance of Convolutional Neural Networks via Self Distillation official implementation&#xff1a; https://github.com/luanyunteng/pytorch-be-your-own-teacher 前言 知识蒸馏作为一种流行的压缩方法&#…

五种多目标优化算法(MOGWO、MOLPB、MOJS、NSGA3、MOPSO)求解微电网多目标优化调度(MATLAB代码)

一、多目标优化算法简介 &#xff08;1&#xff09;多目标灰狼优化算法MOGWO 多目标应用&#xff1a;基于多目标灰狼优化算法MOGWO求解微电网多目标优化调度&#xff08;MATLAB代码&#xff09;-CSDN博客 &#xff08;2&#xff09;多目标学习者行为优化算法MOLPB 多目标学习…

ps5ps4游戏室如何计时?计费系统怎么查看游戏时间以及收费如何管理

ps5ps4游戏室如何计时&#xff1f;计费系统怎么查看游戏时间以及收费如何管理 1、ps5ps4游戏室如何计时&#xff1f; 下图以佳易王计时计费软件V17.9为例说明 在开始计时的时候&#xff0c;只需点 开始计时按钮&#xff0c;那么开台时间和使用的时间长度项目显示在屏幕上&am…

如何判断一个题目用“贪心/动态规划“还是用“BFS/DFS”方法解决

1 总结 1.1 贪心、动态规划和BFS/DFS题解的关系 一般能使用贪心、动态规划解决一个问题时&#xff0c;使用BFS&#xff0c;DFS也能解决这个题&#xff0c;但是反之不能成立。 1.2 2 贪心 -> BFS/DFS 2.1 跳跃游戏1和3的异同 这两道题&#xff0c;“跳跃游戏”&#xf…

靡靡之音 天籁之声 ——Adobe Audition

上一期讲到了和Pr配合使用的字幕插件Arctime Pro的相关介绍。相信还记得的小伙伴应该记得我还提到过一个软件叫做Au。 当人们对字幕需求的逐渐满足&#xff0c;我们便开始追求更高层次的享受&#xff0c;当视觉享受在进步&#xff0c;听觉享受想必也不能被落下&#xff01; Au即…

Flutter桌面应用开发之毛玻璃效果

目录 效果实现方案依赖库支持平台实现步骤注意事项话题扩展 毛玻璃效果&#xff1a;毛玻璃效果是一种模糊化的视觉效果&#xff0c;常用于图像处理和界面设计中。它可以通过在图像或界面元素上应用高斯模糊来实现。使用毛玻璃效果可以增加图像或界面元素的柔和感&#xff0c;同…

一、深入简出串口(USRT)通信——基本概念。

一、前言 串口到底是什么&#xff1f;简单来说一句话就可以解释&#xff0c;串口就是一种通信协议。 看到这里可能大家会觉得你这不是放屁么&#xff0c;说了跟没说一样。所以这里做前言来描述&#xff0c;大家要先对通信协议有一个下意识地认识才能在学习串口的时候不至于迷茫…

spring循环依赖

Bean的生命周期 这里不会对Bean的生命周期进行详细的描述&#xff0c;只描述一下大概的过程。 Bean的生命周期指的就是&#xff1a;在Spring中&#xff0c;Bean是如何生成的&#xff1f; 被Spring管理的对象叫做Bean。Bean的生成步骤如下&#xff1a; Spring扫描class得到Bean…

yolo系列中的一些评价指标说明

文章目录 一. 混淆矩阵二. 准确度(Accuracy)三. 精确度(Precision)四. 召回率(Recall)五. F1-score六. P-R曲线七. AP八. mAP九. mAP0.5十. mAP[0.5:0.95] 一. 混淆矩阵 TP (True positives)&#xff1a;被正确地划分为正例的个数&#xff0c;即实际为正例且被分类器划分为正例…

计算机编程基础教程,中文编程工具下载,编程构件组合按钮

计算机编程基础教程&#xff0c;中文编程工具下载&#xff0c;编程构件组合按钮 给大家分享一款中文编程工具&#xff0c;零基础轻松学编程&#xff0c;不需英语基础&#xff0c;编程工具可下载。 这款工具不但可以连接部分硬件&#xff0c;而且可以开发大型的软件&#xff0c…

人力资源管理后台 === 登陆+主页灵鉴权

目录 1. 分析登录流程 2. Vuex中用户模块的实现 3.Vue-cli代理解决跨域 4.axios封装 5.环境区分 6. 登录联调 7.主页权限验证-鉴权 1. 分析登录流程 传统思路都是登录校验通过之后&#xff0c;直接调用接口&#xff0c;获取token之后&#xff0c;跳转到主页。 vue-elemen…

C++二分查找:统计点对的数目

本题其它解法 C双指针算法&#xff1a;统计点对的数目 本周推荐阅读 C二分算法&#xff1a;得到子序列的最少操作次数 本文涉及的基础知识点 二分查找算法合集 题目 给你一个无向图&#xff0c;无向图由整数 n &#xff0c;表示图中节点的数目&#xff0c;和 edges 组成…

HTTP状态码:如何修复 404 Not Found错误?

互联网上各种类型的网站非常多&#xff0c;无论用户还是网站运营者不可避免的会遇到404 Not Found错误&#xff0c;如果遇到404错误&#xff0c;我们应该如何解决呢&#xff1f; 对于用户 检查拼写错误 如果您是遇到错误的用户&#xff0c;请仔细检查 URL 是否有任何拼写错误…

【Flutter 常见问题系列 第 1 篇】Text组件 文字的对齐、数字和字母对齐中文

TextStyle中设置height参数即可 对齐的效果 Text的高度 是根据 height 乘于 fontSize 进行计算的、这里指定heiht即可、不指定的会出现 无法对齐的情况&#xff0c;如下&#xff1a; 这种就是无法对齐的情况

决策树(第四周)

一、决策树基本原理 如下图所示&#xff0c;是一个用来辨别是否是猫的二分类器。输入值有三个&#xff08;x1&#xff0c;x2&#xff0c;x3&#xff09;&#xff08;耳朵形状&#xff0c;脸形状&#xff0c;胡须&#xff09;&#xff0c;其中x1{尖的&#xff0c;圆的}&#xf…

R语言实现Lasso回归

一、Lasso回归 Lasso 回归&#xff08;Least Absolute Shrinkage and Selection Operator Regression&#xff09;是一种用于线性回归和特征选择的统计方法。它在回归问题中加入了L1正则化项&#xff0c;有助于解决多重共线性&#xff08;多个特征高度相关&#xff09;和特征选…