【多线程初阶】Thread类常见方法以及线程的状态

多线程初阶系列目录

持续更新中

1.第一次认识线程
2.Thread类常见方法以及线程的状态


文章目录

  • 多线程初阶系列目录
  • 前言
  • 1. Thread 类及常见方法
    • 1.1 常见构造方法
    • 1.2 常见属性
    • 1.3 重要方法
      • 1.3.1 启动一个线程 ---- start()
      • 1.3.2 中断一个线程 ---- interrupt()
      • 1.3.3 等待一个线程 ---- join()
      • 1.3.4 获取当前线程 ---- currentThread()
      • 1.3.5 休眠当前线程 ---- sleep()
  • 2. 线程的状态
    • 2.1 观察线程所有的状态
    • 2.2 线程状态和状态转移的意义
  • 总结


前言

本文是属于多线程初阶内容系列的, 如果还没有学习过之前文章的, 请先移步博主的之前的文章进行学习, 本文就是在学会线程的创建后, 再带大家认识一下 Thread 类以及其常见的方法, 再给大家讲解一下线程都有哪些状态.

关注收藏, 开始学习吧🧐


1. Thread 类及常见方法

1.1 常见构造方法

通过我们上篇文章的学习, 我们已经学会了如何创建一个线程, 创建线程的方式主要有两种, 一种是继承 Thread 类, 一种是实现 Runnable 接口, 分别对应着下面, 第一和第二种构造方法, 而第三第四种构造方法, 是在第一第二种的基础上, 加了一个 name 参数, 效果是在创建线程对象时, 可以给其进行命名.

五种常见构造方法(第五种了解即可):

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable 对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group, Runnable target)线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("我的线程");
Thread t4 = new Thread(new MyRunnable(), "我的线程");

利用 jconsole 给大家简单演示一下取了别名的效果.

public class ThreadDemo6 {public static void main(String[] args) {Thread t = new Thread( () -> {while (true) {System.out.println("hello t");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}, "我的线程");t.start();}}

在这里插入图片描述
可以看到, 在线程中有一个名为 “我的线程” 的线程, 是由我们自己命名的.

1.2 常见属性

每个线程, 都有自己的名称, 状态, 优先级, 上下文, 记账信息等等 (之前在讲到进程中这些属性时, 其实都是线程的, 只不过之前谈到的进程是属于只有一个线程的进程).

属性获取方法
IDgetId()
名称getName()
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被中断isInterrupted

ID, 名称, 状态, 优先级都比较好理解, 我们重点讲一下后台进程, 存活, 被中断的这三个属性.

  • ID 是线程的唯一标识, 不同线程不会重复.
  • 名称是各种调试工具需要用到的.
  • 状态表示线程当前所处的一个情况, 下面我们会进一步说明.
  • 优先级高的线程理论上来说更容易被调度到.
  • isDaemon() 是否后台线程: true 表示的是后台线程, false 表示的是前台线程. 那么前台线程和后台线程有什么区别呢? 后台线程不会阻止 Java 进程结束, 哪怕后台线程还没有执行完, Java 进程该结束时就结束了. 而前台线程会阻止 Java 进程结束, 必须得 Java 进程中的所有前台线程都执行结束后, Java进程才能结束. 我们创建的线程默认是前台的. 可以通过 setDaemon() 方法来设置成后台.
  • isAlive() 是否存活: 描述系统内核里的那个线程, 是否存活. 怎样判定线程是否存活呢? 线程的入口方法执行完毕, 此时系统中对应的线程就没了, 调用该方法结果就是 false.
  • isInterrupted 是否被中断: 描述系统内核里的那个线程, 是否被中断. 线程的中断问题, 下面我们进一步说明, 在之后的重要方法 interrupt() 中会重点讲述, 这里我们知道即可.

给大家看一个程序演示一下这些属性:

public class ThreadDemo {public static void main(String[] args) {Thread thread = new Thread(() -> {for (int i = 0; i < 10; i++) {try {System.out.println(Thread.currentThread().getName() + ": 我还活着");Thread.sleep(1 * 1000);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + ": 我即将死去");});System.out.println(Thread.currentThread().getName() + ": ID: " + thread.getId());System.out.println(Thread.currentThread().getName() + ": 名称: " + thread.getName());System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());System.out.println(Thread.currentThread().getName() + ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName() + ": 后台线程: " + thread.isDaemon());System.out.println(Thread.currentThread().getName() + ": 活着: " + thread.isAlive());System.out.println(Thread.currentThread().getName() + ": 被中断: " + thread.isInterrupted());thread.start();while (thread.isAlive()) {}System.out.println(Thread.currentThread().getName() + ": 状态: " + thread.getState());}
}

在这里插入图片描述

1.3 重要方法

下面我们重点介绍一下 Thread 类中的几个重要方法.

1.3.1 启动一个线程 ---- start()

之前我们已经看到了如何通过覆写 run 方法创建一个线程对象, 但线程对象被创建出来并不意味着线程就开始运行了.

  • 覆写 run 方法是提供给线程要做的事情的指令清单
  • 线程对象可以认为是把 李四、王五叫过来了
  • 而调用 start() 方法, 就是喊一声: “行动起来!” , 线程才真正独立去执行了
    在这里插入图片描述

这里也有一个很重要的问题需要大家思考一下, 这是一道很经典的面试题, 请说明Thread类中run和start的区别:

这里, 我们从方法的作用功能, 及运行结果的区别分别说明.

  • 作用功能不同:
    • run 方法的作用是描述线程具体要执行的任务.
    • start 方法的作用是真正的去申请系统线程.
  • 运行结果不同:
    • run 方法是一个类中的普通方法, 主动调用和调用普通方法一样, 会顺序执行一次.
    • start 调用方法后, start 方法内部会调用 Java 本地方法(封装了对系统底层的调用)真正的启动线程, 并执行 run 方法中的代码, run 方法执行完成后线程进入销毁阶段.

调用 start 方法, 才是真的在操作系统的底层创建出一个线程.

1.3.2 中断一个线程 ---- interrupt()

中断一个线程, 这里就是字面意思, 就是让一个线程停下来, 也就是线程的终止. 本质上来说, 让一个线程终止, 办法只有一种, 就是让该线程的入口方法执行完毕.

给大家再用上面的例子讲一下:

李四一旦进到工作状态, 他就会按照行动指南上的步骤去进行工作, 不完成是不会结束的. 但有时我们需要增加一些机制, 例如老板突然来电话了, 说转账的对方是个骗子, 需要赶紧停止转账, 那张三该如何通知李四停止呢? 这就涉及到我们的停止线程的方式了.

目前常见的让线程的入口方法结束的方式有以下两种:

  1. 通过共享的标记来进行沟通, 也就是给线程中设置一个共享标记位.
  2. 调用 Thread 类中的 interrupt() 方法来通知.

示例1: 使用自定义的变量来作为标志位.

public class ThreadDemo7 {// 设置标志位public static boolean isQuit = false;public static void main(String[] args) {Thread thread = new Thread(() -> {while (!isQuit) {System.out.println("还没结束");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("我结束啦");});// 开启一个thread线程thread.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}// 3s 后, 在主线程中修改标志位isQuit = true;}}

在这里插入图片描述
示例2: 使用 Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替自定义标志位.

上面我们是使用自己创建的变量来控制循环, 而 Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

方法说明
public void interrupt()中断对象关联的线程, 如果线程正在阻塞, 则以异常方式通知, 否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置, 调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置, 调用后不清除标志位

使用 thread 对象的 interrupted() 方法通知线程结束.

public class ThreadDemo8 {public static void main(String[] args) {Thread t = new Thread(() -> {// 两种方法均可以while (!Thread.interrupted()) {
//            while (Thread.currentThread().isInterrupted()) {System.out.println("还没结束");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();// 注意此处的 breakbreak;}}System.out.println("我结束啦");});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}// 把 t 内部的标志位给设置成 truet.interrupt();}}

thread 收到通知的方式有两种:

  1. 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起, 则以 InterruptedException 异常的形式通知, 清除中断标志.
    • 当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择忽略这个异常, 也可以利用 break 跳出循环结束线程.
  2. 否则, 只是内部的一个中断标志被设置, Thread 可以通过
    • Thread.interrupted() 判断当前线程的中断标志被设置, 清除中断标志.
    • Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置, 不清除中断标志.

这种方式通知收到的更及时, 即使线程正在 sleep 也可以马上收到.

示例3: 观察标志位是否清除

标志位是否清除, 就类似于一个开关.

  • Thread.isInterrupted() 相当于按下开关, 开关自动弹起来了. 这个称为 “清除标志位”
  • Thread.currentThread().isInterrupted() 相当于按下开关之后, 开关弹不起来, 这个称为 “不清除标志位”.
  1. 使用 Thread.interrupted() , 线程中断会清除标志位.
public class ThreadDemo9 {public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.interrupted());}}, "慧天城");t.start();t.interrupt();}}

可以看到, 输出结果中, 只有一开始是 true, 剩下都是 false, 因为标志位被清除了.
在这里插入图片描述

  1. 使用 Thread.currentThread().isInterrupted() , 线程中断标记位不会清除.
public class ThreadDemo10 {public static void main(String[] args) {Thread t = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().isInterrupted());}});t.start();t.interrupt();}}

结果全部都是 true, 因为标志位没有被清除.
在这里插入图片描述

1.3.3 等待一个线程 ---- join()

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束, 最多等 millis 毫秒
public void join(long millis, int nanos)同理, 但可以更高精度

线程之间是并发执行的, 操作系统对于线程的调度, 是无序的. 无法判断两个线程谁先执行结束, 谁后执行结束.

有时, 我们需要等待一个线程完成它的工作后, 才能进行自己的下一步工作.

看下面这段代码:

public class ThreadDemo11 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("hello t");});t.start();System.out.println("hello main");}
}

这段代码是先输出 hello main 还是 hello t 呢? 这是无法确定的, 虽然这个代码实际执行时, 大部分情况下都是先出 hello main, 但是也不能排除特定情况下, 主线程 hello main 还没有执行到, 先输出 hello t 的情况也会发生.

对于我们程序员来说, 这种情况是不能发生的, 因为我们需要有一个明确的执行顺序, 来确保程序不会出现 bug, 这时我们就可以使用线程等待来实现, 也就是 join() 方法.

public class ThreadDemo11 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {System.out.println("hello t");});t.start();// 使用join方法t.join();System.out.println("hello main");}}

在 t.join() 执行时, 如果 t 线程还没有结束, main 线程就会阻塞等待, 代码都到这一行就会先停下来, 暂时不参与 CPU 的调度执行了.

在这里, 我们是在 main 线程中, 调用 t.join(), 意思就是让 main 线程先等待 t 线程结束, 再继续往下执行, 注意是只有 main 线程进入阻塞, 其余线程均不受影响.

补充t.join();

  1. main 线程在调用 t.join 的时候, 如果 t 线程还在运行, 此时, main 线程堵塞, 直到 t 执行完毕后( t 的 run 执行结束后), main 才从阻塞中解除, 才继续执行.
  2. main 线程调用 t.join 的时候, 如果 t 线程已经结束了, 此时 join 不会阻塞 main 线程, 就会立即往下执行.

但无论哪种情况, 都可以保证 t 线程是最先结束的那个

join 方法还有别的版本, 可以填写参数, 作为 “超时时间” (等待的最大时间).
在这里插入图片描述

  • join 的无参数版本, 效果就是 “死等”, 等待的那个进程什么时候结束, 就什么时候继续往下执行.
  • join 的有参数版本, 则是指定最大超过时间, 如果等待的时间到了上限, 还没等到, 就不等了.

1.3.4 获取当前线程 ---- currentThread()

这个方法, 我们应该就非常熟悉了, 这里也不过多讲解了.

方法说明
public static Thread currentThread();返回当前线程对象的引用
public class ThreadDemo {public static void main(String[] args) {Thread thread = Thread.currentThread();System.out.println(thread.getName());}
}

1.3.5 休眠当前线程 ---- sleep()

这个也是我们比较熟悉一组方法, 有一点要记得, 因为线程的调度是不可控的, 所以, 这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的.

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis 毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠
public class ThreadDemo {public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(3 * 1000);System.out.println(System.currentTimeMillis());}
}

2. 线程的状态

操作系统中的线程, 自身是有一个状态的, 在 Java Thread 中进行了对系统线程的进一步封装, 于是把这里的状态又进一步的精细化了.

2.1 观察线程所有的状态

线程的状态是一个枚举类型 Thread.State

public class ThreadState {public static void main(String[] args) {// 查看线程所有的状态for (Thread.State state : Thread.State.values()) {System.out.println(state);}}
}

在这里插入图片描述

  • NEW: 安排了工作, 还未开始行动, 代表系统中的线程还没创建出来, 只是有了个 Thread 对象.
  • RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作.
  • BLOCKED: 表示排队等着其他事情, 等待锁出现的状态.
  • WAITING: 表示排队等着其他事情, 使用 wait 方法出现的状态.
  • TIMED_WAITING: 表示排队等着其他事情, 指定时间等待 sleep 方法.
  • TERMINATED: 工作完成了, 系统中的线程执行完了, Thread 对象也还在.

2.2 线程状态和状态转移的意义

在这里插入图片描述
大家不要被这个状态转移图吓到, 我们重点是要理解状态的意义以及各个状态的具体意思.
在这里插入图片描述
还是我们之前的例子:

刚把李四、王五找来,还是给他们在安排任务,没让他们行动起来,就是 NEW 状态.
当李四、王五开始去窗口排队,等待服务,就进入到 RUNNABLE 状态。该状态并不表示已经被银行工作人员开始接待,排在队伍中也是属于该状态,即可被服务的状态,是否开始服务,则看调度器的调度.
当李四、王五因为一些事情需要去忙,例如需要填写信息、回家取证件、发呆一会等等时,进入 BLOCKEDWATINGTIMED_WAITING 状态. 这些状态有什么区别, 我们之后再进行了解. 如果李四、王五已经忙完,为 TERMINATED 状态.
所以,之前我们学过的 isAlive() 方法,可以认为是处于不是 NEW 和 TERMINATED 的状态都是活着的.


总结

✨ 本文主要讲解了 Thread 类及常见方法以及线程的状态是什么, 举了一些通俗的例子来帮助大家理解. 重点需要掌握 Thread 类中的一些重要方法, 以及 start 方法和 run 方法的区别, 还有线程都有哪些状态.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

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

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

相关文章

oled拼接屏优势详解

湖北省是中国中部地区的一个省份&#xff0c;拥有着丰富的资源和广阔的市场。在这个省份中&#xff0c;随着科技的不断发展&#xff0c;越来越多的企业开始使用oled拼接屏来展示自己的产品和服务。那么&#xff0c;什么是oled拼接屏呢&#xff1f;它有哪些优势和应用场景呢&…

【LeetCode: 16. 最接近的三数之和 | 双指针专题 】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

(六)人工智能应用--深度学习原理与实战--理解张量与运算图

Tensorflow名称中的Tensor即张量&#xff0c;不仅仅是Tensorflow&#xff0c;几乎所有的深度学习平台都以张量为基本的数据结构。简单来说&#xff0c;张量就是多维数组&#xff0c;本质上是一种数据容器&#xff0c;它可以有任意维度&#xff0c;比如矩阵就是二维张量(二维数组…

基于 jmeter 和 shell 的接口性能自动化

目录 前言&#xff1a; 1. 总体需求 2. 实现流程 3.准备工作 4.具体实现 4.1 用例执行 主流程脚本 4.2 服务器监控 监控脚本&#xff1a; 服务器监控脚本 4.3 生成 html 报告 html 样式表 发邮件脚本 前言&#xff1a; 基于JMeter和Shell的接口性能自动化是一种有…

如何在 Blender 中更快地渲染?这些技巧需要知道

Blender 是一款开源 3D 建模和动画软件&#xff0c;动画艺术家、产品设计师和游戏创作者经常使用。Blender 不仅允许用户创建 2D 对象或角色并对其进行动画处理以获得 3D 模型&#xff0c;而且 Blender 背后还有强大的艺术家和计算机科学家社区&#xff0c;不断改进代码以提供功…

Appium: Windows系统桌面应用自动化测试(三) 【脚本操作】

Appium: Windows系统桌面应用自动化测试 【脚本操作】 一、常用操作1、添加被测程序1.1示例一&#xff1a;通过程序路径指定应用程序&#xff0c;例如指定写字板程序路径。1.2示例二&#xff1a;通过程序ID指定应用程序&#xff0c;例如指定计算器ID。1.3 应用程序ID&#xff0…

尚硅谷Linux学习笔记

文章目录 1. Linux概述2. Linux目录结构3. Linux操作命令3.1 vim编辑命令3.1.1 一般模式3.1.2 编辑模式3.1.3 指令模式 3.2 网络相关命令3.3 系统管理3.4 帮助命令3.4.1 man 获得帮助信息3.4.2 help 获得 shell 内置命令的帮助信息3.4.3 常用快捷键 3.5 文件目录类3.5.1 pwd、e…

「提高你的CSS技能」:15个重要的CSS属性详解

这篇文章介绍了15个重要的CSS属性&#xff0c;旨在提高读者的CSS知识和技能。文章以清晰的方式解释了每个属性的作用和用法&#xff0c;并提供了相应的示例代码。通过这篇文章&#xff0c;读者可以了解到一些有趣且实用的CSS属性。 1:in-range 和:out-of-range 伪类 CSS的:in…

基于51单片机+SHT30设计的环境温度与湿度检测设备(IIC模拟时序)

一、项目介绍 当前文章介绍基于51单片机和SHT30传感器设计的环境温度与湿度检测设备。设备采用IIC模拟时序通信协议&#xff0c;能够实时监测环境的温度和湿度&#xff0c;并将数据通过LCD显示屏显示出来&#xff1b;可以广泛应用于室内环境监测、气象观测、农业温室监测等领域…

音视频开发实战03-FFmpeg命令行工具移植

一&#xff0c;背景 作为一个音视频开发者&#xff0c;在日常工作中经常会使用ffmpeg 命令来做很多事比如转码ffmpeg -y -i test.mov -g 150 -s 1280x720 -codec libx265 -r 25 test_h265.mp4 &#xff0c;水平翻转视频&#xff1a;ffmpeg -i src.mp4 -vf hflip -acodec copy …

常用的访问控制权限模型DAC RBAC

常用的访问控制权限模型DAC RBAC 文章目录 常用的访问控制权限模型DAC RBACLinux 自主访问控制与强制访问控制术语概念存取访问控制 Access Control自主访问控制强制访问控制 基于角色的权限控制模型RBAC模型管理方法RBAC0的管理命令RBAC0的系统支持方法RBAC0的高级审查持方法 …

Table Recognition Metric: 表格识别算法评测工具包及相关评测基准数据集

Table Recognition Metric 该库用于计算TEDS指标&#xff0c;用来评测表格识别算法效果。可与魔搭-表格识别测试集配套使用。TEDS计算代码参考&#xff1a;PaddleOCR 和 DAVAR-Lab-OCR 使用说明&#xff1a; Install package by pypi.pip install table_recognition_metricRu…