【Java系列】详解多线程(二)——Thread类及常见方法(下篇)

个人主页:兜里有颗棉花糖
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创
收录于专栏【Java系列专栏】【JaveEE学习专栏】
本专栏旨在分享学习Java的一点学习心得,欢迎大家在评论区交流讨论💌

目录

  • 一、启动一个线程-start()方法
  • 二、终止一个线程(重点)
      • lambda变量捕获
  • 三、等待一个线程-join()
  • 四、获取当前对象的引用
  • 五、休眠当前线程

一、启动一个线程-start()方法

在操作系统中创建线程时,通常会同时创建相应的PCB并将其加入到线程管理的数据结构中,比如线程链表或线程队列(此步骤是由操作系统内核来完成的)。

调用 start 方法, 才真的在操作系统的底层创建出一个线程
解释:start 方法会通过调用系统API向操作系统请求创建一个新的线程,并分配相应的资源。这个请求将由操作系统内核处理,内核将为线程分配所需的资源。至于线程什么时候创建完成是由操作系统内核说了算的
一旦操作系统内核完成资源的分配和初始化,线程就被创建出来了。此时,操作系统会将线程状态设置为就绪状态,表示该线程已经准备好被调度执行。线程调度器将在适当的时机选择一个就绪的线程,并将其分配给可用的CPU核心来执行。调用start方法是在操作系统底层创建一个线程的触发点。

另外,start方法的执行是在一瞬间就被执行完成的。因为start方法这是负责向操作系统中请求创建出一个线程,start方法执行完成之后,代码就会立即执行start方法后续的代码逻辑。

二、终止一个线程(重点)

我们知道,run方法执行完毕之后线程就结束了。这里我们说的终止线程就是想办法让run方法快速执行完毕(正常情况下run方法没有执行完的话线程是不会突然就结束了的,除非是特别极端的特殊情况,比如拔电源)。

方式一)手动设置标志位:
我们可以通过手动设定标志位的方式来让run方法快速执行完毕。
举例代码如下:

public class Demo08 {
    public static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(!isQuit) {
                System.out.println("hello thread!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();

        // 主线程这里执行一些其它的代码逻辑
        Thread.sleep(3000);
        // 修改前面设定的标志位
        isQuit = true;
        System.out.println("t线程至此终止!!!");
    }
}

运行结果如下:
在这里插入图片描述
上述代码的执行结果其实是不稳定的,有时候可能会打印出来4个hello thread!!!因为sleep操作是存在误差的,比如如果真实的sleep时间比3000毫秒多的话,这里可能打印出来的就是4个hello thread!!!;如果这里真实的sleep时间比3000毫秒少的话,可能这里打印出来的就可能是两个hello thread!!!

lambda变量捕获

现在将上述的代码进行细微的更改,请看:
在这里插入图片描述
为什么这里的isQuit变量如果写作成员变量就不会报错,如果写作局部局部变量就会报错呢?

上述的错误原因是由于lambda变量捕获的原因,下面我们来进行解释:Lambda 表达式可以在其定义所在的方法的上下文中访问和使用外部变量,即Lambda表达式可以捕获外部变量。被Lambda表达式捕获的外部变量必须是final或是事实上final的。
上述代码中写的lambda表达式相当于一个回调函数,它的执行时机是在一个线程被创建好了之后,在另外一个线程内部被调用执行的。所以,lambda表达式的执行时机会稍后一些。所以这就导致后续我们执行lambda表达式的时候,局部变量isQuit已经被销毁了(isQuit局部变量是跟随main方法的),换句话来说main线程已经结束了,但是lambda表达式依然是在继续执行的。
上述这种情况是客观存在的,但是如果让lambda表达式去访问一个已经销毁了的变量这显然是不合适的。所以lambda表达式引入了变量捕获这样的机制
lambda表达式的变量捕获机制:lambda表达式其实并不是直接访问的外部的变量,而是将外部的变量进行复制,即复制到lambda表达式内部(这样就解决了变量生命周期的问题)。
lambda表达式的变量捕获是有条件限制的:Lambda表达式内部访问的外部变量必须是final或者事实上是final(effectively final)。事实上是final的(即effectively final)意思就是变量可以不被final修饰,但是在代码中我们不能修改此变量,此时我们也可以称该变量是final effectively。如果对某变量进行修改的话,此时lambda表达式就不能对改变量进行变量捕获(之所以java中这样设定变量捕获是因为java是通过复制的方式来实现变量捕获机制的,如果外边的变量更改了但是lambda表达式内部的变量没有修改的话此时就很容易对代码产生歧义)。
相比之下,JS语言实现变量捕获的机制跟java中是有所区别的,JS并不是通过复制的方式来实现变量捕获的,而是通过直接改变外部变量的生命周期来保证lambda表达式可以访问到外部的变量,因此JS中的变量捕获的变量没有final或者effectively final的限制
在这里插入图片描述

以上是手动设置标志位的方式,其实在Thread类中已经为我们提供好了标志位:Thread标志位,下面来看代码演示:

public class Demo09 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            // 这里的Thread.currentThread()其实就是t引用
            // lambda表达式是在t构造之间就被定义好的。所以编译器构造的lambda表达式看到t之后就会认为t引用是一个还没有初始化的对象。
            while(!Thread.currentThread().isInterrupted()) {
                System.out.println("hello world!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        // 这里是把标志位设置为true
        t.interrupt();
    }
}

解释:while(Thread.currentThread().isInterrupted())
Thread.currentThread()可以获取到当前线程的对象,currentThread方法是Thread类提供的一个静态方法,在哪个线程中调用这个方法我们就可以获得哪个线程对象的引用。
isInterrupted()Thread类提供的一个标志位,如果为true的话代表线程应该要结束;如果为false的话代表线程可以不必结束。isInterrupted()用于检查当前线程是否被中断,并返回一个布尔值。如果线程没有被中断,该方法将返回false;如果线程被中断,该方法将返回true。

代码运行结果如下:
在这里插入图片描述
代码抛出异常后会继续打印hello world!!!,然后循环下去。
代码解释:代码中的t线程陷入睡眠之后又被interrupt唤醒了;如果我们手动设置标志位的话,是没有办法唤醒t线程的。
当一个线程正在睡眠(通过调用Thread.sleep()方法)时,如果其他线程调用了该线程的interrupt()方法,会导致正在睡眠的线程被强制中断,抛出InterruptedException异常。
注意:上述代码中,interrupt()方法是由主线程调用的。(当一个线程A需要中断另一个线程B时,它可以调用线程B的 interrupt() 方法。这个调用会将线程B的中断状态标志位设置为 true,即表示线程B被请求中断。)
在上述代码中,当t线程被interrupt()方法唤醒时(举个栗子,比如我们设定的sleep(1000),但是此时才过去10毫秒,但是线程依然会被唤醒),它的中断标志位会被设置为true。而sleep()方法会抛出InterruptedException异常,同时会清除中断标志位。因此,在上述代码中,当sleep()方法被interrupt()方法唤醒时,它会抛出InterruptedException异常,并清除中断标志位。因此,前面设置的标志位会被清除。此时,中断标志位会被重新设置为false
当中断标志位被重新设置为false之后,while循环会继续进行打印操作。

重点:上述的代码大家一定要好好进行理解,尤其是中断标志位那个地方,有很多的小点需要大家注意。

上述代码中,sleep被唤醒的同时,中断标志位被重新设定为了false;之后,线程会继续执行下去,但是如果我们想要让线程结束的话,此时我们只需要在catch之后加上break就可以了。演示代码如下:
在这里插入图片描述
运行结果如下:在这里插入图片描述
异常信息打印出来之后,代码中的while循环就会被break,即使我们不清除标志位的话,代码依然就会结束(当然加上break之后,标志位依然会被清除然后标志位会被重新设置为false)。
还有一点就是如果我们不想看到程序运行结果中的异常信息的话,我们可以直接注释掉catch中的e.printStackTrace();用于打印异常的堆栈跟踪信息)就好了

关于sleep唤醒之后可以执行哪些操作:
我们这里依然是以刚刚上述的代码进行举例,如果你忘记了的话,请看下图:
在这里插入图片描述

sleep被唤醒之后,开发人员一般可以有以下几种操作方式(给开发者留下了一定的操作空间,具体要干什么还是要根据具体的时机需求来决定):

  • 立即结束线程(如上图就是加上break之后就会立即结束线程)
  • 执行其它的一些代码逻辑,执行完这些代码逻辑之后再结束线程(即再catch中执行执行其它的代码逻辑,等到这些代码逻辑执行结束之后再break就可以了)
  • 或者忽略终止请求继续循环下去(即catch中不写break就好了)

判断中断标志位的两种方式:

  • Thread.interrupted():这是一个静态方法,此方法在判定标志位的同时会对标志位进行清除
  • Thread.currentThread().isInterrupted():这是一个成员方式(推荐使用这种方式)判断标志位的时候不会进行清除

三、等待一个线程-join()

我们知道多个线程是并发执行的,具体的执行过程都是由操作系统进行调度的,而操作系统调度线程的过程是完全随机的,随机意味着我们不知道这些线程的执行的先后顺序是怎样的。而这里的等待线程就是用来规划线程结束顺序的手段

举个栗子:现在又A、B两个线程,我们希望B线程先结束而A线程后结束,所以可以让A线程中调用B.jion()的方法,此时B线程没有执行完的话,那么A线程就会进入阻塞状态(阻塞状态的意思就是代码不继续往后执行了,即该线程暂时不去cpu上参与调度)。相当于给B线程留下了执行时间,当B线程执行完毕(即run方法执行完毕)之后,A线程就会从阻塞状态中恢复回来继续往后执行。

请看代码演示:

public class Demo10 {
    public static void main(String[] args) {
        Thread b = new Thread(() -> {
            for(int i = 0;i < 5;i++) {
                System.out.println("hello b线程!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("b线程结束了!!!");
        });
        Thread a = new Thread(() ->{
            for(int i = 0;i < 3;i++) {
                System.out.println("hello a线程!!!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("a线程结束了!!!");
        });
        b.start();
        a.start();
    }
}

代码演示结果如下:
在这里插入图片描述
上述代码中是a线程先结束b线程后结束。
现在我们让b线程先结束a线程后结束的话,代码如下图:
在这里插入图片描述
运行结果如下:
在这里插入图片描述
如上图b线程先结束,a线程后结束。

关于阻塞阻塞状态的解释:阻塞状态就是线程代码不在继续往后执行了,即该线程不再参与cpu调度了。
sleep方法可以让线程进入阻塞状态,但是sleep方法的阻塞是有时间限制的。
而join方法的阻塞可以说是没有时间限制,如果有两个线程A、B,倘若是B.join()的话,如果B线程没有执行结束的话,那么A线程就会死等下去即A线程将永远不被执行知道B线程执行结束。
显然,jion方法的死等这样的方式是有些不大合适的,jion方法还有另外一种形式,即public void join(long millis)(等待线程结束,最多等millis毫秒),这里的参数相当于一个最大等待时间。

另外还有一点:join方法是可以被interrupt方法唤醒的,其实sleep、join、wait方法产生阻塞之后都是可以被interrupt唤醒的(这几个方法在被唤醒之后会自动清除标志位,这一点和sleep类似)。

四、获取当前对象的引用

获取当前对象引用可以使用该方法:public static Thread currentThread();(可以返回当前线程对象的引用),在哪个线程中调用该方法就可以获取到哪个线程的引用。

五、休眠当前线程

休眠当前线程方法如下:

  • public static void sleep(long millis) throws InterruptedException :休眠当前线程millis毫秒
  • public static void sleep(long millis, int nanos) throws InterruptedException:可以更高精度的休眠

好了,本文到这里就结束了,希望友友们可以支持一下一键三连哈。嗯,就到这里吧,再见啦!!!

在这里插入图片描述

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

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

相关文章

删除员工信息和全局异常处理

删除员工&#xff1a; 删除员工信息&#xff0c;根据员工的id删除其实批量删除就是一种特殊的批量删除&#xff0c;所以&#xff0c;删除员工的功能&#xff0c;我们只需要开发一个接口就可以了。 删除员工的逻辑分析&#xff1a; controller层获取请求的参数&#xff1a; 接收…

大模型微调的“温度”参数,原来影响的是 softmax

大家好啊&#xff0c;我是董董灿。 在对大模型进行微调训练时&#xff0c;经常会看到几个重要的超参数&#xff0c;用来控制大模型生成文本的效果。 其中一个超参数叫做 Temperature&#xff0c;中文名字叫温度&#xff0c;初见时很是不解&#xff0c;为啥一个模型还有温度这个…

【问题处理】—— lombok 的 @Data 大小写区分不敏感

问题描述 今天在项目本地编译的时候&#xff0c;发现有个很奇怪的问题&#xff0c;一直提示某位置找不到符号&#xff0c; 但是实际在Idea中显示确实正常的&#xff0c;一开始以为又是IDEA的故障&#xff0c;所以重启了IDEA&#xff0c;并执行了mvn clean然后重新编译。但是问…

36、什么是池化算法

池化算法也是 CNN 网络中非常常见的算法。 池化这一算法理解起来比较简单,从名字中或许可以看到一些东西:从一个像素池子中选取一些有代表性的像素出来。 常见的池化有最大池化和平均池化。最大池化就是从像素池子中选取最大值出来,而平均池化就是从像素池子中选取平均值出…

Ganache结合内网穿透实现远程或不同局域网进行连接访问

文章目录 前言1. 安装Ganache2. 安装cpolar3. 创建公网地址4. 公网访问连接5. 固定公网地址 前言 Ganache 是DApp的测试网络&#xff0c;提供图形化界面&#xff0c;log日志等&#xff1b;智能合约部署时需要连接测试网络。 Ganache 是一个运行在本地测试的网络,通过结合cpol…

一文搞清楚“并发与并行”“串行与并行”“线程与进程”的区别

目录 &#x1f436;6.1 并发与并行 &#x1f436;6.2 串行与并行 1. 基本概念 2. 举个&#x1f330; 3. 适用场景 &#x1f436;6.3 线程与进程 1. 基本概念 2. 进程与线程的区别 3. 线程调度: &#x1f436;6.1 并发与并行 并行&#xff1a;指两个或多个事件在同一时…

2023年OceanBase开发者大会-核心PPT资料下载

一、峰会简介 2023年OceanBase开发者大会主要涵盖了OceanBase的最新技术进展、产品更新以及开发者工具的发布。大会发布了OceanBase 4.1版本&#xff0c;公布了两大友好工具&#xff0c;升级了文档的易用性&#xff0c;并统一了企业版和社区版的代码分支。这些举措全面呈现了O…

【算法题】开源项目热度榜单(js)

解法 const lines ["4","8 6 2 8 6","camila 66 70 46 158 80","victoria 94 76 86 189 211","athony 29 17 83 21 48","emily 53 97 1 19 218", ]; const lines2 ["5","5 6 6 1 2","…

Python:pipdeptree 语法介绍

相信大家在按照一些包的时候经常会碰到版本不兼容&#xff0c;但是又不知道版本之间的依赖关系&#xff0c;今天给大家介绍一个工具&#xff1a;pipdeptree pipdeptree 是一个 Python 包&#xff0c;用于查看已安装的 pip 包及其依赖关系。它以树形结构展示包之间的依赖关系&am…

json Deserialization of Python Objects

openweathermap.json {"coord": {"lon": 114.0683, "lat":22.5455},"weather":[ {"id": 803, "main":"Clouds", "description":"多云", "icon":"04d"}],"…

产品Axure的安装以及组件介绍

Axure介绍&#xff1a; Axure是一款用户体验设计工具&#xff0c;可以用于创建交互式原型、线框图和设计文档。它支持快速原型开发、界面设计、信息架构、流程图和注释等功能&#xff0c;可以帮助设计师快速地创建和共享交互式原型&#xff0c;从而更好地与客户和团队协作。 …

利用Pytorch预训练模型进行图像分类

Use Pre-trained models for Image Classification. # This post is rectified on the base of https://learnopencv.com/pytorch-for-beginners-image-classification-using-pre-trained-models/# And we have re-orginaized the code script.预训练模型(Pre-trained models)…

回溯热门问题

关卡名 回溯热门问题 我会了✔️ 内容 1.组合总和问题 ✔️ 2.分割回文串问题 ✔️ 3.子集问题 ✔️ 4.排列问题 ✔️ 5.字母全排列问题 ✔️ 6.单词搜索 ✔️ 1. 组合总和问题 LeetCode39题目要求&#xff1a;给你一个无重复元素的整数数组candidates和一个目标整数 ta…

数据结构学习 12字母迷宫

dfs 回溯 剪枝 这个题和dfs有关&#xff0c;但是我之前没有接触过&#xff0c;我看了这一篇很好的文章&#xff0c;看完之后写的答案。 我觉得很好的总结&#xff1a; dfs模板 int check(参数) {if(满足条件)return 1;return 0; }void dfs(int step) {判断边界{相应操作}尝试…

自考 00023高等数学考点整理

空间直角坐标系 右手法则 向量 点到点的距离 点到直线的距离点到平面的距离向量平行向量垂直向量投影向量数乘 a*b axb(行列式计算)直线夹角、直线与平面夹角平面点法式方程空间直角坐标系 右手法则向量数量积、向量积 平行四边形法则、三角形法则 第二章 多元函数 微分学…

VS2022 将项目打包,导出为exe运行

我有一个在 VS2022 上开发的程序&#xff0c;基于.net 6框架, 想打包成 .exe程序&#xff0c;以在另一个没有安装VS的机器上运行&#xff0c;另一个机器是Win7系统&#xff0c;上面安装了.net 6框架。 虽然网上很多教程&#xff0c;需要安装Project Installer&#xff0c;配置A…

从零开始创建一个项目,springBoot+mybatisPlus+mysql+swagger+maven

一&#xff0c;前提 从零开始创建一个项目&#xff0c;绑定了数据库 用到的技术栈&#xff1a;springBootmybatisPlusmysqlswaggermaven 二&#xff0c;创建项目步骤 1&#xff0c;创建项目 创建出来的项目结构如图所示 2&#xff0c;修改配置文件 因为我比较习惯yml语言&…

算法:最小生成树

文章目录 生成树Kruskal算法Prim算法 本篇总结的是最小生成树算法 生成树 连通图中的每一棵生成树&#xff0c;都是原图的一个极大无环子图&#xff0c;即&#xff1a;从其中删去任何一条边&#xff0c;生成树就不在连通&#xff1b;反之&#xff0c;在其中引入任何一条新边&…

路由器交换机配置备份工具

本文主要介绍fast-backup 2.0软件的使用&#xff0c;fast-backup 2.0是可以在任何Windows系统上运行的网络运维软件&#xff0c;帮助运维人员减少大量重复的交换机等设备的配置下载工作&#xff0c;支持的厂商有华为和华三的网络设备和安全设备。 功能特性&#xff1a; 支持S…

提升数据分析效率:Amazon S3 Express One Zone数据湖实战教程

前言 什么是 Amazon S3&#xff1f;什么是 S3 Express One Zone&#xff1f;实现概述 技术架构组件实现步骤概览 第一步&#xff1a;构建数据湖的基础第二步&#xff1a;选择并查看数据集第三步&#xff1a;在 Athena 中搭建架构第四步&#xff1a;数据转换与优化第五步&#x…