多线程(二):创建线程关键属性终止线程

news2024/11/26 16:31:58

目录

1、run & start

 2、Thread类常见的属性和方法

2.1 构造方法

2.2 属性

3、后台进程 & 前台进程

4、后台线程的判断和设定——isDaemon & setDaemon

5、线程是否存活——isAlive

6、终止一个线程

6.1 lambda变量捕获

6.2 currentThread & isInterrupted & interrupt

6.2.1 sleep被唤醒


1、run & start

在多线程(一)的博客中,为大家介绍了五种创建线程的方式,但从大类上分也就是两大类——分别是通过自定义类继承Thread以及自定义类实现Runnable接口(解耦合)来实现。

这两种方式中,都用到了start方法以及run方法,这里再次强调一下两种方法的作用及区别。

  • start方法的作用是真正在系统中创建线程并启动线程(由JVM调用操作系统的api完成线程创建操作)。
  • start是jvm提供的方法,本质上是调用操作系统提供的api。
  • start是native修饰的本地方法,说明是在JVM内部实现的,并由C++代码实现(JVM由C++实现)
  •  每个Thread对象,都只能start一次,也就是说一个Thread对象只能创建一个线程。每想创建一个新的线程,就需要创建一个新的Thread对象。(“日抛”,一次性的)
  • run方法是线程的入口方法,不需要手动调用,当新的线程启动后就会自动调用。
  • run方法相当于一个“回调函数”。什么是“回调函数”?简单来说,就是这个方法自己不用,让别人去用。举个例子:我们使用优先级队列(堆)时,需要指定比较规则,其中Comparable的compareTo方法以及Comparator的compare方法就是回调函数。

 2、Thread类常见的属性和方法

2.1 构造方法

相信大家对于前两个构造方法并不陌生,就是我们上文所提到创建线程的两种方法。

而第三种和第四种,则是额外给创建的线程自定义名字。

给线程起名字,其作用就是方便我们程序员调试(通过名字描述线程的作用),哪怕线程的名字相同也不会影响线程的执行。

如下图所示:

我们就可以观察到我们自定义的两个名为t1、t2的两个线程正在运行。 

但是,我们并没有发现主线程main,这是为什么呢?

——这是因为主线程中已经结束了(main中并没有死循环代码,主线程main早已执行完毕了~~)。

我们之前的认知时,main是程序的入口,main执行完程序(进程)就执行完毕了,其实这只是针对于单线程程序来说的。在多线程中,非也~~

我们当然也可以通过写死循环代码来观察主线程main。

public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("hello t1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("hello t2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t2");
        t2.start();

        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

2.2 属性

  • ID就类似于pid,是线程的唯⼀标识,不同线程不会重复。
  • 名称,即我们给线程自定义的名字,没有自定义则为系统默认的名字。
  • 状态,优先级这里就不再赘述。

线程中含有多个属性,我们可以通过Thread中相应的方法进行获取。 


3、后台进程 & 前台进程

我们需要先了解 前台线程 与 后台线程。

前台线程 的作用大,能够直接影响进程是否执行完毕。当所有的前台线程都执行完后,进程才能结束,要是有一个前台线程没有结束,整个进程都不能结束。

我们自己创建的线程,包括main主线程,都是前台线程。

后台线程,又称为 守护线程,Deamon就是守护的意思。说明这个线程 是默默守护的,他们的存在,不会影响 进程 的结束。

一些JVM自带的线程,就是后台线程,他们的存在左右不了进程的结束。简单来说,如果进程要结束,不会问这些后台线程的意见,进程要是想结束,那他们就必须跟着结束,进程结束后,他们也会随之结束。

有些后台线程存在的意义不是很大,即使没有他们,也没啥影响。但是,有些后台线程是JVM提供的一些具有特殊功能的线程,会跟随整个进程持续执行(不能随便离开),比如:垃圾回收进程。

前台线程 和 后台线程 都是有多个的。

如果有多个前台线程,必须所有的 前台线程 结束后,进程才能结束。

4、后台线程的判断和设定——isDaemon & setDaemon

  • isDaemon方法用来 判断 线程是否为 后台线程。
  • setDaemon方法是将 前台线程 修改 为后台线程。注意:必须在线程创建前修改(即调用start方法前使用setDaemon方法修改)

将 前台线程 修改为 后台线程 后,这些线程将无力阻止进程的结束。

此时,仅有一个前台线程main,当main结束后,整个进程将会结束,虽然t1处于循环状态,但t1为后台线程,不会影响整个进程的状态。

  • 进程间存在 父子关系 ,但线程间不存在。

比如 IDEA 本身就是一个进程,打开 IDEA 后运行的Java代码又是一个进程,这就是进程间的父子关系。 

5、线程是否存活——isAlive

isAlive方法用来判断线程是否存活。

在Java中,Thread创建的对象和线程是一一对应的关系。

但是Thread对象的生命周期和所创建的线程的生命周期是不同的,可能存在线程已经结束,但是Thread对象依旧存活的情况。

我们将线程设置为存活3s,但是发现却打印出了4个true,这是由于并发时操作系统随机调度的原因,主线程的第四次打印,和thread线程结束,谁先谁后,是不一定的~~

在这里就是,在主线程第四次打印true时(主线程先被调度执行),thread线程还有一口气,但是马上就无了~~


6、终止一个线程

"终止" 指的是让一个线程结束,且不会再恢复了。

当一个线程的入口方法执行完毕后,线程也就随即结束了。

那么让一个线程提前终止,其实就是让run方法尽早return而已。

6.1 lambda变量捕获

我们可以通过变量设置,控制线程的存活时间。

此时可以得到我们想要的结果。

此时的isFinish为成员变量,但是当我们把isFinish放到main方法中当做局部变量时,却发生了报错,这是为什么呢?

它告诉我们,isFinish应该是final类型或者事实上为final类型的,也就是说isFinish的值不能发生改变。

这是因为,在lambda内部,触发了“变量捕获”的语法,而我们在下面的代码中对isFinish进行了修改,所以发生了报错。

因为lambda本来就是用于“回调函数”使用的,而 回调函数 执行时机是不确定的,可能是很久以后才进行线程的创建,而此时main这个主线程可能早已经执行完毕了,那么main内部的局部变量早已销毁。

为解决上述问题,Java将被捕获的变量拷贝了一份,传到了lambda的内部,以此让lambda能够使用外面的局部变量。但是,拷贝的变量,就意味着这个变量和原本的变量不是同一个东西,当一方进行修改时,另一方不会随之改变。而这边变,那边又不变,就会给程序猿带来更多的问题~~

为解决这个问题,Java大佬们这样决定,就根本不允许这个变量进行修改。这就是“变量捕获”的语法,被捕获的变量必须是final或事实上为final的(不是final类型,但是变量值没有改变)。

而当我们将isFinish设置为成员变量时,此时就不再是“变量捕获”语法的范畴,而切换为“内部类访问外部类成员”的语法(lambda本来就是实现函数式接口的匿名内部类的简化形式,本质上就是一个内部类),而“内部类访问外部类成员”的语法本来就是正确的。

对象本体以及其中的成员变量的生命周期都是由GC(垃圾回收)进行管理的,不会随方法的结束而销毁。

当final修饰引用类型时,不能修改引用的指向(不能将这个引用指向别的对象),但是可以修改引用指向的对象本体。

6.2 currentThread & isInterrupted & interrupt

  • isInterrupted方法的作用就是判断线程是否被终止。
 

lambda表达式的执行是在对象new之前,此时的thread引用还没有被声明。

我们需要使用Thread的静态方法currentThread获取线程的引用。

  • currentThread方法在哪个线程内部被调用,获取的就是哪个线程的Thread引用。而这个代码是在 lambda 中 (也就是在 thread 线程的入口run方法中) 调用的,获取的就是 thread 线程的引用。(类似于this)

故 Thread.currentThread().isInterrupted() 的作用就是,判断线程是否被终止了。

  • interrupt方法的作用是去主动终止线程(修改内部的boolean变量(标志位)值)
 

当我们设置好终止线程的方法后,运行发现有异常抛出,被JVM捕获,导致程序直接终止:

这是由于调用interrupt方法的原因:

  1. interrupt方法不仅会修改线程内部的boolean变量值
  2. thread线程内绝大部分时间处于sleep的休眠状态(其他代码执行速度很快),而interrupt方法会提前唤醒sleep(sleep休眠时间未到就被唤醒了),而sleep就会抛出InterruptedException异常
  3. 抛出异常后,由于catch块内没有将异常处理好,则会直接带走整个进程

而我们不想让线程"掀桌",我们可以修改 IDEA 默认的处理异常的方式,使用 break 替换,使线程更加优雅的终止:

public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(() -> {
            //这个代码是在 lambda 中 (也就是在 thread 线程的入口run方法中) 调用的,获取的就是 thread 线程的引用
            while (!Thread.currentThread().isInterrupted()) {
                try {
                    System.out.println("hello thread");
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // 唤醒sleep,抛出异常,catch捕获后再次抛出新异常
                    // 线程的 “掀桌” 操作
                    //throw new RuntimeException(e);
                    break;
                }
            }
            System.out.println("thread 结束");
        }, "thread");
        thread.start();

        Thread.sleep(3000);
        // 主动终止线程
        System.out.println("main 尝试终止线程thread");
        thread.interrupt();

    }

6.2.1 sleep被唤醒

虽说是优雅了,但是依然是interrupt唤醒了sleep,抛出异常,通过catch逻辑来进行的线程终止操作。

如果我们在catch块中将break去除后(catch中空着),线程还会终止吗???

我们发现,当去掉break后,即使在主线程中通过interrupt方法修改了t线程的标志位,但线程依旧没有终止,这是为什么呢???

其实,这是由于sleep在搞鬼。

  • 正常情况下,我们通过interrupt方法将isInterrupted内的标志位设置为了true
  • 但是由于sleep方法是被唤醒的,sleep在被唤醒之后,又把这个标志位的值设置回了false

所以当循环在次执行到循环条件判定时,循环就会继续执行。

其实Java对于sleep的这种设定,是为了让程序员在catch块中有更多的选择空间——当外部通知线程终止时,程序员可以选择立即终止、也可以选择一会再终止、也可以选择不终止。

我们程序猿可以根据线程的执行情况,再次自行设定线程终止的时机,让线程执行出一些"阶段性"结果时,再来终止线程,以至于避免线程执行一半就被强行终止,避免出现执行出"不上不下"的结果。


END

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

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

相关文章

atomic physics

​​​​​​​ https://www.youtube.com/watch?v6F8Wjblb0vE&listPLX2gX-ftPVXVqAS_q3OfJDmPn8-EQld_r&index18

Python 工具库每日推荐 【logging】

文章目录 引言Python日志记录的重要性今日推荐:logging模块主要功能:使用场景:快速上手基本使用代码解释实际应用案例案例:文件日志记录器案例分析高级特性日志过滤器上下文管理器扩展阅读与资源优缺点分析优点:缺点:总结【 已更新完 TypeScript 设计模式 专栏,感兴趣可…

钢管X射线 焊缝缺陷检测数据集,3408张x射线焊缝缺陷图像,也有更多的图像数据集,可以进行扩充 目标检测任务。可制作上位机软件。

钢管X射线 焊缝缺陷检测数据集,3408张x射线焊缝缺陷图像,也有更多的图像数据集,可以进行扩充 目标检测任务。可制作上位机软件。 B lowhole Undercut Broken arc Crack Overlap Slag inclusion L ack of fusion Hollow bead 钢管X射线焊缝缺陷…

正则表达式 | Python、Julia 和 Shell 语法详解

正则表达式在网页爬虫、脚本编写等众多任务中都有重要的应用。为了系统梳理其语法,以及 Python、Julia 和 Shell 中与正则表达式相关的工具,本篇将进行详细介绍。 相关学习资源:编程胶囊。 基础语法 通用语法 在大多数支持正则表达式的语…

Github 2024-10-11 Java开源项目日报 Top9

根据Github Trendings的统计,今日(2024-10-11统计)共有9个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目9TypeScript项目1Vue项目1C++项目1JeecgBoot 企业级低代码开发平台 创建周期:2062 天开发语言:Java, Vue协议类型:Apache License 2.…

除了Devops、DevSecOps和TestOps ,您还了解ITOps吗?

大家可能听说过Devops、DevSecOps、TestOps ,但是对于ITOps这个词儿可能会感到陌生,今天就带大家来了解一下什么事ITOps ITOps 信息技术运营 — 通常称为 IT 运营,或是 ITOps ,是实施、管理、 交付和支持 IT 服务的过程&#x…

Odoo:免费开源的装备制造行业信息化解决方案

概述 满足装备制造行业MTO、ETO、MTS等多种业务模式,从个性化的订单选配、多层级计划管理模式、复杂的物料齐套规划、频繁的设计变更管理、精细化制造执行和用料管控、精准的售后服务等行业特性,提供一站式整体解决方案。 行业趋势洞察 个性化定制 洞察…

FPGA中的亚稳态

一、建立时间和保持时间 对于数字系统而言,建立时间(setup time,Tsu)和保持时间(hold time,Th)是数字电路时序的基础。数字电路系统的稳定性,基本取决于时序是否满足建立时间和保持时…

详细版的Jsoncpp的使用,包括在VS环境下配置

目录 准备环境VS 环境下配置编译使用 基础概述Json 数组Json 对象 Jsoncpp 的使用ValueFastWriterReader示例 如果想要 Json 部署在 Linux 上 参考: https://blog.csdn.net/2303_76953932/article/details/142703683?spm1001.2014.3001.5502 C中原生不支持 Json,所…

ClickHouse 数据保护指南:从备份到迁移的全流程攻略

一、背景 运行3年的clickhouse需要迁移机房,迁移单库单表的140亿条的数据。采用clickhouse-backup 的方式进行备份迁移,打包备份,再加上数据拷贝,数据恢复 一共花费30分钟。数据在一定量级,避免使用SQL 导入导出的方式…

配置 Visual studio 2022 Connector C++ 8.0环境 连接MySQL

Connector C 8.0 环境配置 1. 配置 Connector C 头文件地址 (1) 在Mysql的安装路径中找到 MySQL\Connector C 8.0\include\jdbc 例如 : C:\Program Files\MySQL\Connector C 8.0\include\jdbc (2) 打开VS2022&…

如何用好 CloudFlare 的速率限制防御攻击

最近也不知道咋回事儿,群里好多站长都反映被CC 攻击了。有人说依旧是 PCDN 干的,但明月感觉不像,因为有几个站长被 CC 攻击都是各种动态请求(这里的动态请求指的是.php 文件的请求)。经常被攻击的站长们都知道,WordPress /Typecho 这类动态博客系统最怕的就是这种动态请求…

塔吊识别数据集 yolo格式 共5076张图片 已划分好训练验证 txt格式 yolo可直接使用

塔吊识别数据集 yolo格式 共5076张图片 已划分好训练验证 txt格式 yolo可直接使用。 类别:塔吊(Tower-crane) 一种 训练数据已划分,配置文件稍做路径改动即可训练。 训练集: 4724 (正面3224 负面1500) 验证集&#xf…

【父子线程传值TransmittableThreadLocal使用踩坑-及相关知识拓展】

文章目录 一.业务背景二.TransmittableThreadLocal是什么?三.问题复现1.定义注解DigitalAngel2.定义切面3.TransmittableThreadLocal相关4.线程池配置信息5.Controller6.Service7.测试结果8.问题分析9 解决办法及代码改造10.最终测试: 四.与 ThreadLocal…

【大模型实战篇】创建有效的大模型提示词Prompt(提示词工程)

1. 背景介绍 随着chatgpt、llama、chatglm、claude AI等一系列生成式 AI 工具的普及,很明显能感受到,个人的生产力得到了大幅地提升。这些生成式算法模型能够帮助我们开发新想法、轻松获取信息,并简化各种个人和职业任务。个人在日常生活、工…

问卷调查毕设计算机毕业设计投票系统SpringBootSSM框架

目录 一、引言‌ ‌二、需求分析‌ 用户角色‌: ‌功能需求‌: ‌非功能需求‌: ‌三、系统设计‌ ‌技术选型‌: ‌数据库设计‌: ‌界面设计‌: ‌四、实现步骤‌ ‌后端实现‌: …

Python快速编程小案例——猜数字

提示:(个人学习),案例来自工业和信息化“十三五”人才培养规划教材,《Python快速编程入门》第2版,黑马程序员◎编著 猜数游戏是一种经典的密码破译类益智游戏,通常由两个人参与。一个人在心中设…

【C++网络编程】(三)多线程TCP服务端程序

文章目录 (三)多线程TCP服务端程序多线程服务端客户端 (三)多线程TCP服务端程序 图片来源:https://subingwen.cn/linux/concurrence 主线程负责监听和连接多个客户端,子线程负责和对应的客户端进行通信&am…

vue后台管理系统从0到1搭建(4)各组件的搭建

文章目录 vue后台管理系统从0到1搭建(4)各组件的搭建Main.vue 组件的初构 vue后台管理系统从0到1搭建(4)各组件的搭建 Main.vue 组件的初构 根据我们的效果来看,分析一下,我们把左边的区域分为一个组件&am…

如何将本地磁盘镜像包部署到docker中(以mysql5_7.tar.gz为例)

1.复制文件到宿主机 2.找到对应目录,docker load docker images就可以看到该镜像啦