【JavaEE】浅谈线程(二)
线程
线程的常见属性
线程属性可以通过下面的表格查看。
•ID 是线程的唯⼀标识,不同线程不会重复
• 名称是各种调试⼯具⽤到(如jconsoloe)
• 状态表示线程当前所处的⼀个情况,下⾯我们会进⼀步说明
• 优先级高的线程理论上来说更容易被调度到
• 关于后台线程,需要记住⼀点:JVM会在⼀个进程的所有非后台线程结束后,才会结束运⾏。
• 是否存活,即简单的理解,为 run 方法是否运⾏结束
• 线程的中断问题,下⾯我们进⼀步说明

线程的状态
线程状态分为New、Terminated、Runnable、Waiting、Timed_Waiting、Blocked
观测线程状态可以通过jconsole观察。
- New 该状态下存在Thread对象,还没有调用start,系统内部PCB还未创建
- Terminated 该状态下表示线程已终止,内部创建的PCB已经销毁,Thread对象还在
- Runnable 该状态下表示线程正在CPU上执行,随时可以被调度。
- Waiting 死等状态进入的阻塞
- Timed_Waiting 带有规定时间的等待
- Blocked 进行锁竞争后出现的阻塞状态
线程状态之间的转换图(省略版)

线程是否存活
在线程生命周期中,根据观察线程中的PCB是否存在。
Thread t = new Thread(()->{});
在这段代码中,线程t只是初始化了一个Thread对象,但是其中的PCB只有在执行了t.start()以后才会被创建出来。
public static void main(String[] args) {Thread t = new Thread(()->{});t.start();try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
在上面的代码中,线程t在启动以后创建了PCB,但是因为t中代码块没有任何代码,所以线程t很快就运行结束了,内核中的线程和PCB马上就被销毁了。而在main线程中,通过sleep停止了2s,因此线程t的PCB被销毁,而t这个对象并没有被gc回收,t仍然是存在的。
在下面的代码中,我加上了t=null,通过这个方法以求解决t对象能够被gc回收。但显然是不行的,通过t=null,可能会导致t线程还没结束,t对象就为空了。
public static void main(String[] args) {Thread t = new Thread(()->{});t.start();t = null;try {Thread.sleep(2000);} catch (InterruptedException e) {throw new RuntimeException(e);}}
因此,Thread给了我们一种属性:isAlive,是否存活。通过这个属性我们可以查看到线程是否存在,并对线程t进行操作。
public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("hello ");}System.out.println("线程t是否存活:"+Thread.currentThread().isAlive());});t.start();Thread.sleep(3000);System.out.println("线程t是否存活:"+ t.isAlive());}
结果如下图

前台线程和后台线程的关系与区别
线程是并发执行的。当然,当线程多了以后,为了能够不让某些线程影响整个进程的结束,更好的利用系统资源。于是为线程设计了前台和后台两个线程方式。在前台线程中,如果本线程运行没有结束,则此时Java进程也无法结束。在后台线程中,即使该线程还处于执行阶段,当前台线程都结束以后,意味着整个Java进程即将结束。那么这时候后台线程就不得不停止执行了。
简单来说,前台线程决定了Java进程的时间,后台线程无法控制整个进程的时间,只能被迫跟着前台线程的结束而结束。
前台线程也可以是多个的,只有最后一个前台线程结束,整个Java进程才结束。
设置线程的前后台
在Java中,main线程,以及默认情况下的线程都属于前台线程。而改变线程前后台的方式可以通过修改线程前后台属性: t.setDaemon(true) 将对应线程转变为后台线程。
接下来使用一个例子进行简单演示
public class Demo1 {public static void main(String[] args) {Thread t = new Thread(() -> {while (true) {for (int i = 0;i<5;i++) {System.out.println("hello thread");}}});t.setDaemon(true);t.start();for (int i = 0; i < 5; i++) {System.out.println("hello main");}}
}
在上面的代码中,我们可以看到,理论上来说线程t应当是进入死循环不断打印"hello thread"的,但是我在线程启动之前,将t线程设置为后台线程。因此,在main线程运行结束以后,垃圾回收机制gc将main线程回收,前台线程结束,后台线程t自然也只能被迫结束。

线程核心操作
启动 t.start()
通过多次的练习,已经明白t.start()执行后创建线程PCB,真正创建线程并开始执行。不再过多赘述
终止 interrupt()
在Java中,终止线程不是简单的停止线程的执行。而是对线程进行提醒,提醒线程是否停止,而真正做决定的还是该线程本身。
1)通过变量控制终止线程
private static boolean isRunning=true;public static void main(String[] args) throws InterruptedException {// boolean isRunning = true;//变量捕获Thread t2 = new Thread(()->{while (isRunning) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t2线程被结束了");});t2.start();Thread.sleep(4000);System.out.println("控制t2线程结束");isRunning = false;}
结果如下图

2)使用线程的interrupt和isInterrupted方法
通过下面的代码进行解释,在这个代码块中,线程t3通过isInterrupted判断是否结束这个循环,而在main线程中,通过t.interrupt方法,将这个值修改为false,抛出RuntimeException异常,结束了当前的线程。
public static void main(String[] args) throws InterruptedException {Thread t3 = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException();//抛出异常并结束}}System.out.println("t2线程被结束了");});t3.start();Thread.sleep(3000);t3.interrupt();}

3)提醒线程而不终止
在下面的代码中,修改了boolean之后,线程t3并没有停止线程,而是打印出异常信息之后继续执行。
在这里出现了一个问题,为什么通过interrupt方法之后修改了boolean值,但是在线程抛出异常之后仍然在继续打印呢?
首先,interrupt方法修改了boolean值的标志位修改为true,在slee过程中,通过interrupt方法强制唤醒线程,在强制唤醒后清除了刚刚的标志位,重新修正回flase。
public static void main(String[] args) throws InterruptedException {Thread t3 = new Thread(()->{while (!Thread.currentThread().isInterrupted()) {System.out.println("hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace(); //打印异常信息而不结束//throw new RuntimeException();//抛出异常并结束}}System.out.println("t2线程被结束了");});t3.start();Thread.sleep(3000);t3.interrupt();}

等待join()
因为线程是抢占式执行的,其调度顺序在系统中是无序的。而这种无序的执行方式不在程序猿的掌控之中,因此我们希望能够通过一些方式控制这些线程。join(等待)就是其中一种方式。
- 基本用法
在下面的代码块中,分为main线程和t两个线程。我们可以注意到,与之前的写法其实是大差不差的,唯一区别的是在try-catch包裹下出现的t.join()方法。
在main线程中使用t.join的作用让main线程进入阻塞等待状态,t线程执行完之后main线程才会接着往下执行。
通过这样的方式在一定程度上解决了线程的控制问题。
public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("thread end");});t.start();try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for (int i = 0; i < 5; i++) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("main end");}
因此我们可以看到结果如下图所示:

- 多个线程的join
在多个线程等待代码了解之前,我们必须了解的是在main线程中调用t1.join,是main线程等待t1,而与其他线程无关,main线程是不会等待其他线程的。当多线程的情况下也是这般理解的。
在下面的代码中,存在t1和t2两个线程,在main线程中,同时调用t1.join()和t2.join() 这意味着main线程只有等t1线程和t2线程全部执行完毕以后才会往下执行。t1和t2线程各自调度执行,互不影响。 所需的时间是t1线程和t2线程运行时间较长的。
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 end");});Thread t2 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t2 end");});t1.start();t2.start();t1.join();t2.join();for (int i = 0; i < 10; i++) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("main end");}
- 有限时间内等待
在上面两个情况下,对于t.join()的方法,只有等待线程t运行结束以后才能进行下一步,属于死等状态。那么如果出现意料之外的情况,线程t无法结束,那么整个程序都会因此卡住,无法执行后续的逻辑,极大降低了系统的容错。
因此Java提供了join带参数的两种方法。通过这两种方法,当线程超过规定时间而不结束,则主线程不再等待,继续执行下面的逻辑。

接下来简单写个demo
在这个demo中,存在三个线程分别为t1、t2、main。在代码中我通过t1.join()让两个线程都进行阻塞状态等待线程t1结束。而对于t2,不相同的是我只让main线程等待2s,超过两秒之后,main线程不再是阻塞状态,而是执行下面的逻辑。
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("hello t1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t1 end");});Thread t2 = new Thread(() -> {try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for (int i = 0; i < 5; i++) {System.out.println("hello t2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("t2 end");});t1.start();t2.start();t1.join();t2.join(2000);//等待2s后main线程自己走了for (int i = 0; i < 5; i++) {System.out.println("hello main");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("main end");}
因此代码结果如下:

总结
多线程有许多常见属性以及各类用法,都是需要熟练掌握的。
源码:多线程相关代码
相关文章:
【JavaEE】浅谈线程(二)
线程 线程的常见属性 线程属性可以通过下面的表格查看。 •ID 是线程的唯⼀标识,不同线程不会重复 • 名称是各种调试⼯具⽤到(如jconsoloe) • 状态表示线程当前所处的⼀个情况,下⾯我们会进⼀步说明 • 优先级高的线程理论上来…...
爬虫:爬取知乎热榜一级评论及回答2024不包含翻页
一、先上结果(注:本文仅为兴趣爱好探究,请勿进行商业利用或非法研究,负责后果自负,与作者无关) 1、爬标题及其具体内容 2、抓标题下的对应回答 3、爬取对应一级评论 二、上流程 1、获取cookies(相信哥哥姐姐…...
AI 编程探索- iOS动态标签控件
需求分析: 标签根据文字长度,自适应标签居中显示扩展 超过内容显示范围,需要换行显示,且保持居中显示 AI实现过程 提问: 回答: import UIKit import SnapKitclass DynamicLabelsContainerView: UIView…...
计算机网络——数据链路层(数据链路层概述及基本问题)
链路、数据链路和帧的概念 数据链路层在物理层提供服务的基础上向网络层提供服务,其主要作用是加强物理层传输原始比特流的功能,将物理层提供的可能出错的物理连接改造为逻辑上无差错的数据链路,使之对网络层表现为一条无差错的链路。 链路(…...
【前端】前端权限管理的实现方式:基于Vue项目的详细指南
前端权限管理的实现方式:基于Vue项目的详细指南 在Web开发中,前端权限管理是一个确保应用安全性和优化用户体验的关键部分。本文将详细介绍前端权限管理的几种实现方式,并通过Vue项目中的代码示例来演示具体实现方法。 前端权限管理的基本实…...
MySQL数据库基础练习系列——教务管理系统
项目名称与项目简介 教务管理系统是一个旨在帮助学校或教育机构管理教务活动的软件系统。它涵盖了学生信息管理、教师信息管理、课程管理、成绩管理以及相关的报表生成等功能。通过该系统,学校可以更加高效地处理教务数据,提升教学质量和管理水平。 1.…...
windowns server2016服务器配置php调用powerpoint COM组件
解决问题:windowns server2016服务器配置php调用powerpoint COM组件 环境: windows server2016 宝塔(nginxmysqlphp7.2) IIS 搭建宝塔: 下载地址:https://www.bt.cn/download/windows.html 安装使用&…...
Git之checkout/reset --hard/clean -f区别(四十二)
简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长! 优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀 优质专栏:多媒…...
MySQL数据库基础练习系列:科研项目管理系统
DDL CREATE TABLE Users (user_id INT AUTO_INCREMENT PRIMARY KEY COMMENT 用户ID,username VARCHAR(50) NOT NULL UNIQUE COMMENT 用户名,password VARCHAR(255) NOT NULL COMMENT 密码,gender ENUM(男, 女) NOT NULL COMMENT 性别,email VARCHAR(100) UNIQUE COMMENT 邮箱 …...
算法设计与分析--考试真题
分布式算法试题汇总选择题简答题算法题 2013级试题2019级试题2021年秋考卷 根据考试范围找相应题目做。 分布式算法试题汇总 选择题 下述说法错误的是___ A 异步系统中的消息延迟是不确定的 B 分布式算法的消息复杂性是指在所有合法的执行上发送消息总数的最大值 C 在一个异步…...
【鸿蒙学习笔记】页面和自定义组件生命周期
官方文档:页面和自定义组件生命周期 目录标题 [Q&A] 都谁有生命周期? [Q&A] 什么是组件生命周期? [Q&A] 什么是组件?组件生命周期 [Q&A] 什么是页面生命周期? [Q&A] 什么是页面?页面生…...
ASPICE与ISO 21434:汽车软件与网络安全标准的协同与互补
ASPICE(Automotive SPICE)与ISO 21434在汽车行业中存在显著的相关性,主要体现在以下几个方面: 共同目标: ASPICE和ISO 21434都旨在提高汽车系统和软件的质量、可靠性和安全性。ASPICE关注汽车软件开发过程的成熟度和…...
视频格式转换方法:如何使用视频转换器软件转换视频
众所周知,目前存在许多不同的视频和音频格式。但我们的媒体播放器、移动设备、PC 程序等仅兼容少数特定格式。例如,如果不先将其转换为 MP4、MOV 或 M4V 文件,AVI、WMV 或 MKV 文件就无法在 iPhone 上播放。 视频转换器允许您将一种视频格式…...
vim操作小诀窍:快速多行添加注释
在使用vim编译python代码的时候,经常碰到需要将一段代码注释的情况,每次都要按“向下” “向左”按钮,将光标移到句首,然后再键入#井号键。如果行数较多,则操作相当繁琐。 vim里面有将一段文字前面加#注释的方法&#…...
无线麦克风领夹哪个牌子好,2024年领夹麦克风品牌排行榜推荐
随着短视频热潮的兴起,越来越多的人倾向于用vlog记录日常生活,同时借助短视频和直播平台开辟了副业。在这一过程中,麦克风在近两年内迅速发展,从最初的简单收音功能演变为拥有多样款式和功能,以满足视频创作的需求。…...
Mybatis入门——语法详解:基础使用、增删改查、起别名、解决问题、注释、动态查询,从入门到进阶
文章目录 1.基础使用1.添加依赖2.在resouces文件下新建xml文件db.properties3.在resouces文件下新建xml文件mybatis-config-xml4.创建一个MybatisUtils工具类5.创建xml文件XxxMapper.xml映射dao层接口6.添加日志5.测试 2.增删改查1.select2.delete3.update4.insert5.模糊查询6.…...
仓库选址问题【数学规划的应用(含代码)】阿里达院MindOpt
本文主要讲述使用MindOpt工具优化仓库选址的数学规划问题。 视频讲解👈👈👈👈👈👈👈👈👈 一、案例场景 仓库选址问题在现代物流和供应链管理中具有重要的应用。因为仓库…...
Docker Compose 一键快速部署 RocketMQ
Apache RocketMQ是一个开源的分布式消息中间件系统,最初由阿里巴巴开发并贡献给Apache软件基金会。RocketMQ提供了高性能、高可靠性、高扩展性和低延迟的消息传递服务,适用于构建大规模分布式系统中的消息通信和数据同步。 RocketMQ支持多种消息模型&am…...
Vscode lanuch.json
Intro 使用launch.json 能够方便的运行需要传很多参数的代码文件 如下: import math import argparse # 1、导入argpase包def parse_args():parse argparse.ArgumentParser(descriptionCalculate cylinder volume) # 2、创建参数对象parse.add_argument(--rad…...
Golang开发:构建支持并发的网络爬虫
Golang开发:构建支持并发的网络爬虫 随着互联网的快速发展,获取网络数据成为了许多应用场景中的关键需求。网络爬虫作为一种自动化获取网络数据的工具,也因此迅速崛起。而为了应对日益庞大的网络数据,开发支持并发的爬虫成为了必…...
以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...
Module Federation 和 Native Federation 的比较
前言 Module Federation 是 Webpack 5 引入的微前端架构方案,允许不同独立构建的应用在运行时动态共享模块。 Native Federation 是 Angular 官方基于 Module Federation 理念实现的专为 Angular 优化的微前端方案。 概念解析 Module Federation (模块联邦) Modul…...
使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台
🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...
MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
C# 表达式和运算符(求值顺序)
求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如,已知表达式3*52,依照子表达式的求值顺序,有两种可能的结果,如图9-3所示。 如果乘法先执行,结果是17。如果5…...
