【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
目录
1. start()
(1) start() 的性质
(2) start() 和 Thread类 的关系
2. 终止一个线程
(1)通过共享的标记结束线程
1. 通过共享的标记结束线程
2. 关于 lamda 表达式的“变量捕获”
(2) 调用interrupt()方法
1. isInterrupted()
2. currentThread()
3. interrupt()
3. join()
4. sleep()
5. Java中线程生命周期的定义
(1) 线程状态
(2) 线程状态转移
(3) 操作系统中线程的生命周期
1. start()
(1) start() 的性质
之前我们已经讲过了,如何通过覆写 run() 方法,创建一个线程对象;但线程对象被创建出来,并不意味着线程就开始运行了。
- 覆写 run() 方法是提供给线程要做的事情的指令清单
- 线程对象可以认为是把李四、王五叫过来了
- 而调用 start()方法,就是喊一声:”行动起来!“,线程才真正独立去执行了。
start() 是Java 标准库/JVM 提供的方法,本质上是调用操作系统的API
- 在 idea 中查看 start() 的原码,发现关键部分被关键字 native 修饰;
- 被native这个关键字,修饰的方法,称为本地方法。
补充:
run() 是线程的入口方法,通过 JVM 自行调用,不需要手动调用;start() 是调用操作系统的 API.
(2) start() 和 Thread类 的关系
- 在 Java中,Thread 对象和操作系统中的线程 — — 对应;
- 每个 Thread对象,都只能调用一次 start() 来创建线程;
- 如果想创建多线程,就必须创建新的Thread 对象;
答: 他们属于是两个不同的输出流,没办法保证输出的顺序。
2. 终止一个线程
如何终止一个线程:
- 通过共享的标记来进行沟通
- 调用interrupt()方法来通知
(1)通过共享的标记结束线程
1. 通过共享的标记结束线程
想要终止一个线程,就是让线程中的入口方法return ,进而使得线程终止。
来看如下代码:
该代码的逻辑为:
让 t 线程执行死循环的打印,现在,我们要修改一下这个代码中的循环终止条件,以结束 t 线程.
为了避免编译器优化而出现 bug ,需要用 volatile 关键字修饰 标志位 (成员变量);
(这个关键字的功能后面介绍)。
程序运行结果:
打印次数并不重要,重要的是随着 isfinish 被更改,t线程也因此结束。
所以让线程结束的关键,就是让线程中的入口方法 run() 能够被返回。
2. 关于 lamda 表达式的“变量捕获”
在上面的代码中还有一个小细节,我们在while的循环判断条件中,引入了一个变量;
引入的变量,是以成员变量的方式,定义这个变量的。
如果把这个变量定义成局部变量,把 isfinish 放入 main 方法中,是否可以实现刚刚的逻辑呢?
我们查看报错原因:
如果对于局部变量 isFinish 不做任何后续修改,那么这个变量是允许被 lamda 捕获的:
补充:
lamda 表达式“变量捕获”的语法,如果针对的对象类型,是引用类型,只要这个引用指向的对象不改变,哪怕这个对象的值被修改,这个引用类型的变量也是允许被 lamda 捕获的。
因为引用类型的局部变量,和引用类型指向的对象本体 的生命周期是不同的;
所以 lamda 的“变量捕获”语法的核心问题,还是成员变量和局部变量的生命周期问题:
对于上图 7 8 点的补充:
- 内部类可以访问外部类的成员,这样的语法不是变量捕获,自然不受到final 或者不能修改变量的限制
- 这里面的差别在于,如果写成局部变量,其生命周期,是跟着当前执行的方法,也就是mian方法走的。
- 就可能会出现,回调函数一执行,发现main方法已经结束;main方法结束,成员变量因此被销毁,所以无法在回调函数中访问到该变量,所以要去拷贝一份
- 但是一拷贝,就会出现,拷贝的新变量,和被拷贝的变量的值,可能会在修改中出现不一致,所以Java才强制限制该变量不能修改
- 而如果是成员变量的话,他的生命周期是让GC来管理的,在lamda中,不用担心访问的变量生命周期失效的问题。对应的,也就不必拷贝,也就不必限制 final 类
(2) 调用interrupt()方法
Java 的 Thread 对象中提供了现成的变量,直接进行判定,不需要自己创建了.
Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位;
Thread 内部,包含了一个boolean类型的变量,作为线程是否被中断的标记.
1. isInterrupted()
方法 | 说明 |
public boolean isInterrupted() | 判断对象关联的线程的标志位是否设置,调用后不清除标志位 |
isInterrupted() 方法,是用于判断当前调用该方法的线程是否终止,返回值为 true / false;
通过 线程对象引用.isInterrupted() 来代替自定义标志位 isFinished:
报错原因:
- 因为 lamda 表达式的定义虽然写在实例对象 new Thread 之后,但是lamda 的定义顺序在 new Thread 之前;
- 也就是 lamda 的定义顺序 ,先于声明 Thread t 的顺序,导致 lamda 表达式无法识别 t。
2. currentThread()
- currentThread() 的作用:返回当前线程对象的引用
-
- currentThread() 被 native 修饰 ,是本地方法;
- 同时也被 static 修饰,静态方法的调用不需要实例化对象,只需要通过类名就可以进行调用;
- 所以在哪个线程调用 currentThread() ,获取到的就是哪个线程的 Thread 引用。
对于下图中的代码,是在 lambda 中 (也就是在 t 线程 的入口方法中) 调用的 currentThread();
Thread.currentThread() 的返回结果就是t :
补充:
在while的循环判断条件中,返回的是Thread类的成员;
注意:
String类的成员,不能通过 引用. 成员 这种写法来访问 String类 中的成员
同理,currentThread() 在 main 方法中调用;
此时 Thread.currentThread() 返回结果就是 主线程 main
总结:在哪个线程调用 currentThread() ,获取到的就是哪个线程的 Thread 引用:
3. interrupt()
方法 | 说明 |
public void interrupt() | 中断对象关联的线程,如果线程正在阻塞,则以异常方 式通知,否则设置标志位 |
public static boolean interrupted() | 判断当前线程的中断标志位是否设置,调用后清除标志位 |
interrupt() 方法,除了设置 boolean变量(标志位)之外,还能够唤醒像 sleep 这样的阻塞方法。
我们来看下面代码的逻辑:
让 t 线程 执行3s 的打印后,main 线程执行 t.interrupt() 终止 t 线程
执行结果:
抛出异常的原因:
使用 thread 对象的 interrupted() 方法,通知线程结束,thread 收到通知的方式有两种:
1. 如果线程因为调用 wait / join / sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通知,清除中断标志。
- 当出现 InterruptedException 的时候,要不要结束线程取决于catch 中代码的写法.可以选择忽略这个异常,也可以跳出循环结束线程(把 catch 代码块中的抛出异常,直接改成break);
2. 否则,只是内部的一个中断标志被设置,thread 可以通过
- Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志;
- Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志;
这种方式通知收到的更及时,即使线程正在sleep 也可以马上收到。
第二种终止线程的方法的总结:
3. join()
方法 | 说明 |
public void join() | 等待线程结束 |
public void join(long millis) | 等待线程结束,最多等 millis 毫秒 |
public void join(long millis, int nanos) | 同理,但可以更高精度 |
使用方法:
从最终的执行结果中,三个打印日志的顺序,我们可以得到以上的代码的执行逻辑 :
- 让 t1 线程在创建好后,执行其中的 run() 方法;
- 此时会 先打印第一个日志,然后 t1线程 执行 join() ,表示 t1 要阻塞等待主线程执行完毕,才可以继续执行;
- 而主线程要执行的,就是休眠 3s 后,打印 主线程结束的日志;
- 主线程结束后,t1 线程的 join() 执行完毕,打印 t1线程的结束日志
通过代码逻辑,我们可以明白 join() 的用法 :
- 如图中的代码,是在 t1 线程中,执行 主线程对象的引用 所调用的 join() 方法,表示让 t1 线程 先等待 主线程 结束,t1 中的 join() 才执行完毕,才可以执行后续 t1 的内容。
总结:
- 在 线程A 中,执行 线程B 对象的引用所调用的 join(),表示让 线程A 阻塞等待 线程B执行完毕;
- 如果不给 join() 传参数,则是无止境地等待 线程B 执行直到结束;
- 传参数则 线程A 会阻塞等待 线程B 执行到一定的时间,会恢复两个线程并发执行的状态。
4. sleep()
sleep() 是我们熟悉的一组方法,有一点要记得:
因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
方法 | 说明 |
public static void sleep(long millis) throws InterruptedException | 休眠当前线程 millis 毫秒 |
public static void sleep(long millis, int nanos) throws InterruptedException | 可以更高精度的休眠 |
5. Java中线程生命周期的定义
(1) 线程状态
在Java 中,线程的生命周期可以细化为以下几个状态:
状态 | 说明 |
New(初始状态) | 线程对象创建后,但未调用start() 方法。 |
Runnable(可运行状态) | 调用start()方法后,线程进入就绪状态,等待CPU 调度。 |
Blocked(阻塞状态) | 线程试图获取一个对象锁而被阻塞。 |
Waiting(等待状态) | 线程进入等待状态,需要被显式唤醒才能继续执行。 |
Timed Waiting(含等待时间的等待状态) | 线程进入等待状态,但指定了等待时间,超时后会被唤醒。 |
Terminated(终止状态) | 线程执行完成或因异常退出 |
(2) 线程状态转移
(3) 操作系统中线程的生命周期:
- 操作系统中线程的生命周期通常包括以下五个阶段
状态 说明 新建(New) 线程对象被创建,但尚未启动。
就绪(Runnable) 线程被启动,处于可运行状态,等待CPU调度执行。
运行(Running) 线程获得CPU资源,开始执行run()方法中的代码。
阻塞(Blocked) 线程因为某些操作(如等待锁、I/O操作)被阻塞,暂时停止执行。
终止(Terminated) 线程执行完成或因异常退出,生命周期结束。
相关文章:

【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
目录 1. start() (1) start() 的性质 (2) start() 和 Thread类 的关系 2. 终止一个线程 (1)通过共享的标记结束线程 1. 通过共享的标记结束线程 2. 关于 lamda 表达式的“变量捕获” (2) 调用interrupt()方法 1. isInterrupted() 2. currentThread() …...

面试题分享11月7日
1、ThreadLocal 是什么 是 Java 中线程的本地方法变量,用来存储每个线程的私有数据,每个线程都有它的独立副本,相互隔离,互不影响 2、ThreadLocal 实现原理 每个 ThreadLocal 都有一个 ThreadLocalMap 对象,用来存储…...

数据结构_哈夫曼树及其应用
构造算法的例子 构造算法的实现 初始化,置权值 int i, m, s1, s2;m 2 * n - 1;for (i 1; i < m; i){HT[i].lch 0;HT[i].rch 0;HT[i].parent 0;}for (i 1; i < n; i){cin >> HT[i].weight;}合并结点 // 创建哈夫曼树for (i n 1; i < m; i){s1…...
从0开始学习机器学习--Day19--学习曲线
一般来说,如果一个算法的表现不理想,那么多半是因为出现了欠拟合或过拟合问题,这种时候我们要做的就是搞清楚出现的是偏差问题还是方差问题,亦或是二者皆有,这有助于我们精准定位问题所在。 之前,我们发现…...

2.索引:深入解析 B+ 树:原理、MySQL 应用及与其他数据结构的对比
B 树是一种高效的平衡树结构,在数据库和文件系统中被广泛应用,尤其在 MySQL 中,InnoDB 存储引擎通过 B 树实现索引结构,提升了大数据量条件下的查询性能。 本文将深入介绍 B 树的原理和设计特点,分析 MySQL 中使用 B …...

[全网最细数据结构完整版]第六篇:3分钟带你吃透栈并模拟实现
目录 1->栈的概念和结构 1.1栈的概念 1.2栈的结构 2->栈的实现 2.1定义关于栈的结构体和各种函数 2.2栈的初始化 STInit 函数 2.3栈的销毁 STDestroy 函数 2.4栈的插入操作 STPush 函数 2.5栈的判断是否为空操作 STEmpty 函数 2.6栈的删除操作 STPop 函数 2.7…...

如何在 Docker 容器中启动 X11 图形界面程序
如何在 Docker 容器中启动 X11 图形界面程序 在使用 Docker 时,我们通常会发现,容器中的图形应用没法直接显示到宿主机的界面上。不过,我们可以通过共享 X11 的 Unix 套接字,让容器把显示数据传递给宿主机的 X11 服务器ÿ…...

pycharm保存是自动格式化
在PyCharm中设置保存时自动格式化代码,可以按照以下步骤进行: 1. 打开设置 在Windows和Linux系统中,可以通过File(文件)->Settings(设置)打开设置窗口;在Mac系统中,…...

.netCore WebAPI中字符串加密与解密
In today’s digital landscape, securing sensitive information is more critical than ever. If you’re using ASP.NET Core, you might store configuration settings in appsettings.json. However, hardcoding sensitive data like connection strings or API keys in p…...

Next.js + Move 石头剪刀布
rock-paper-scissors 写在前面 本地 源码:https://github.com/zcy1024/SuiStudy/tree/main/rock-paper-scissors # 或其它等价的命令来安装依赖并将项目跑起来 pnpm install pnpm run dev # http://localhost:3000/在线(如果没过期的话) …...

[面试]关于Redis 的持久化你了解吗
Redis的持久化是指Redis服务器在关闭或重启时,将内存中的数据保存到磁盘上的一种机制。Redis支持多种持久化方式。 一、RDB(Redis Database)持久化 RDB持久化是Redis默认采用的持久化方式,它将Redis在某个时间点的数据保存到磁盘上…...

Systemd:tmpfiles
Systemd提供了一个结构化的可配置方法来管理临时文件和目录,即systemd-tmpfiles,可以创建、删除和管理临时文件的服务。 $ systemctl list-units --all | grep systemd-tmpfilessystemd-tmpfiles-clean.service load…...

【Flutter 内嵌 android 原生 View以及相互跳转】
Flutter 内嵌 android 原生 View以及相互跳转 一. 内嵌android 原生View二、android 与 flutter 相互跳转 一. 内嵌android 原生View 在android 工程的包名下,也可在MainActivity创建 android 原生view ,继承PlatformView // 1.自定义textview public st…...

python externally-managed-environment 外部管理环境
https://realpython.com/python-virtual-environments-a-primer/?refyaolong.net#why-do-you-need-virtual-environments 简而言之, pip 默认会将您安装的所有外部包放置在 Python 安装路径/site-packages/ 的文件夹中一些Linux 和 macOS操作系统 预装了内部的 P…...

前端 | MYTED单篇TED词汇学习功能优化
文章目录 📚实现效果🐇before🐇after 📚模块实现解析🐇html🐇css🐇javascript 📚实现效果 🐇before 点击TED单篇词汇表按钮,选择对应TED打卡号,…...

64 mysql 的 表锁
前言 我们这里来说的就是 我们在 mysql 这边常见的 几种锁 行共享锁, 行排他锁, 表意向共享锁, 表意向排他锁, 表共享锁, 表排他锁 我们前面了解了行共享锁, 行排他锁, 表意向共享锁, 表意向排他锁 等等相关 我们这里 来看一下 表共享锁, 表排他锁 的获取, 以及 和 其他表级…...

【计网不挂科】计算机网络期末考试——【选择题&填空题&判断题&简述题】题库(1)
前言 大家好吖,欢迎来到 YY 滴计算机网络 系列 ,热烈欢迎! 本章主要内容面向接触过C的老铁 本博客主要内容,收纳了一部门基本的计算机网络题目,供yy应对期中考试复习。大家可以参考 欢迎订阅 YY滴其他专栏!…...

ajax关于axios库的运用小案例
AJAX案例 图书管理 四大功能: 展示图书删除图书编辑图书信息新增图书 步骤 1.bootstrap弹窗来实现新增和编辑图书时出现的弹窗 有两种方案: a.可以用自带的属性来进行弹窗的显示和隐藏 b.可以通过JS进行控制,此操作可以进行自定义&am…...

微搭低代码入门01变量
目录 1 变量的定义2 变量的赋值3 变量的类型4 算术运算符5 字符串的连接6 模板字符串7 检查变量的类型8 解构赋值8.1 数组的解构赋值8.2 对象的解构赋值 9 类型转换9.1 转换为字符串9.2 转换为数字9.3 转换为布尔值 总结 好些零基础的同学,在使用低代码的时候&#…...

盘点2024年10款视频剪辑,哪款值得pick!!
在这个短视频盛行的时代,如何让我们的故事更生动有趣呢?那就要对短视频进行修饰了。这就需要借助视频剪辑工具:而一款好的工具不仅仅是视频的“美颜”,更是创意的灵魂所在!想象一下,运用一款功能齐全的剪辑…...

苹果手机照片批量删除:一键清理,释放空间
在数字化时代,iPhone不仅是我们沟通的桥梁,也是记录生活的重要工具。然而,随着时间的积累,手机中的照片数量不断增加,不仅占用大量存储空间,也让设备变得缓慢。苹果手机照片批量删除成为了一个普遍的需求。…...

《AI 大模型:重塑软件开发新生态》
《AI 大模型:重塑软件开发新生态》 一、AI 大模型引领软件开发新潮流二、AI 大模型在软件开发中的优势(一)提高开发效率(二)减少错误与提升质量(三)激发创新与拓展功能 三、AI 大模型在软件开发…...

uniapp(API-Promise 化)
一、异步的方法,如果不传入 success、fail、complete 等 callback 参数,将以 Promise 返回数据异步的方法,且有返回对象,如果希望获取返回对象,必须至少传入一项 success、fail、complete 等 callback 参数,…...

【考研数学 - 数二题型】考研数学必吃榜(数二)
数学二 suhan, 2024.10 文章目录 数学二一、函数❗1.极限1.1求常见极限1.2求数列极限1.2.1 n项和数列极限1.2.2 n项连乘数列极限1.2.3 递推关系定义的数列极限 1.3确定极限式中的参数1.4无穷小量阶的比较 2.连续2.1判断是否连续,不连续则判断间断点类型2.2证明题 二…...

Redis生产问题(缓存穿透、击穿、雪崩)——针对实习面试
目录 Redis生产问题什么是缓存穿透?如何解决缓存穿透?什么是缓存击穿?如何解决缓存击穿?缓存穿透和缓存击穿有什么区别?什么是缓存雪崩?如何解决缓存雪崩? Redis生产问题 什么是缓存穿透&#x…...

android openGL中模板测试、深度测试功能的先后顺序
目录 一、顺序 二、模板测试 1、概念 2、工作原理 3、关键函数 三、深度测试 1、概念 2、工作原理 3、关键函数 三、模板测试和深度测试的先后顺序 一、顺序 在Android OpenGL中,模板测试(Stencil Testing)是在深度测试࿰…...

CCF PTA 编程培训师资认证2021年7月真题- C++兑换礼品
【题目描述】 小零和小壹是两个爱玩游戏的小孩,他俩平时最擅长的是解谜游戏,可今天 遇到了一个有点难的算法问题,希望能得到你的帮助。 他们面对的是一个电子装置,正面有 n 个排成一列的按钮,按钮上贴着编号 1~n 号的…...

火山引擎云服务docker 安装
安装 Docker 登录云服务器。 执行以下命令,添加 yum 源。 yum update -y yum install epel-release -y yum clean all yum list依次执行以下命令,添加Docker CE镜像源。更多操作请参考Docker CE镜像。 # 安装必要的一些系统工具 sudo yum install -y yu…...

【taro react】 ---- 常用自定义 React Hooks 的实现【六】之类渐入动画效果的轮播
1. 效果 2. 场景 css 效果实现:可以看到效果图中就是一个图片从小到大的切换动画效果,这个效果很简单,使用 css 的 transform 的 scale 来实现图片的从小到大的效果,切换就更加简单了,不管是 opacity 还是 visibility 都可以实现图片的隐藏和显示的切换。React.Children.m…...

基础算法练习--滑动窗口(已完结)
算法介绍 滑动窗口算法来自tcp协议的一种特性,它的高效使得其也变成了算法题的一种重要考点.滑动窗口的实现实际上也是通过两个指针前后遍历集合实现,但是因为它有固定的解题格式,我将其单独做成一个篇章. 滑动窗口的解题格式: 首先,定义两个指针left和right,与双指针不同的…...