当前位置: 首页 > news >正文

Thread 类

目录

1、创建线程

2、Thread的常见构造方法

3、Thread的几个常见属性

 4、启动一个线程 - start()

5、中断(终止)一个线程


1、创建线程

方法1:继承 Thread 类,重写 run(上节讲了)

方法2:实现 Runnable 接口,重写 run

1. 实现 Runnable 接口

2. 创建 Thread 类实例,调用Thread 的构造方法时将 Runnable 对象作为target参数

3. 调用 start 方法

说明:

1. Runnable 对象,表示一个 “可以执行的任务",就是一段要执行的代码,最终还是要通过 Thread 真正创建线程;把线程里要执行的代码,通过 Runnable 来表示,而不是通过直接重写 Thread run来表示

2. 和直接重写 Thread  run 的区别,是看线程要执行的任务的定义,是需要放到 Thread 里面,还是放到外面(Runnable)

3. 把要执行的逻辑单独放到 Runnable 里,目的是为了解耦合。让要执行的任务本身,和线程这个概念,能够解耦合,从而后续如果变更代码,采用 Runnable 这样的方案,代码的修改会更简单(比如不通过线程执行这个任务,通过线程池、协程等其他方式)

4. 耦合:两个代码的关联越大,耦合就越大。我们不希望见到耦合太高,高耦合不利于修改代码,改一个代码可能会改坏一片

5. 内聚:一个项目中,有很多代码,有很多文件很多类,把有关联的各种代码,放到一起,只要和某个功能逻辑相关的东西,都放在这一块,这就是高内聚。如果某个功能的代码,这一块那一块,就是低内聚

6. 编写代码的基本原则:高内聚(一个模块之内,有关联的东西放一起),低耦合(模块之间,依赖尽量小,影响尽量小)

方法三(推荐):使用匿名内部类(本质上就是方法一 )

匿名内部类:Thread t = new Thread() { }

1. 创建了一个 Thread 的匿名子类

2. { } 里可以编写子类的定义代码(属性、方法、重写方法)

3. 创建了这个匿名内部类的实例,并且把实例的引用赋值给 t


一般如果某个代码是 “一次性” 的,就可以使用匿名内部类的写法,这样就可以少定义一些类了

方法四:使用 Runnable,匿名内部类

方法五:lambda表达式创建 Runnable 子类对象(针对三和四进一步改进)

lambda表达式本质上就是一个 “匿名函数”,最主要的用途,就是作为 “回调函数”

2、Thread的常见构造方法

说明:

1. 使用不带参数的构造方法,必须要重写 Thread的 run 方法

2. 第三第四个构造方法,name 参数表示当前线程的名字,具体是什么样的名字,不影响线程的执行。命名的意义,就是通过名字描述线程,方便程序员调试

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

上述代码中有 t1、t2、t3 三个线程,使用工具查看:

上图中,少了 main 线程。不是没有,是 main 方法在创建完 t1、t2、t3 三个线程后,就执行完毕了,main 方法执行完毕,主线程就结束了。

在以前,main方法执行完,程序就结束了(进程)。实际上,以前是只针对单线程程序的


前台线程:main 线程虽然结束了,但是 t1,t2,t3 还在,所以进程仍然存在,这样的线程,就称为 “前台线程”

后台线程:上图中除了 t1,t2,t3,其他的线程都是JVM自带的线程,他们的存在不影响进程结束(即使他们继续存在,如果进程要结束了,他们也随之结束了),这样的线程,就称为 “后台线程”

3、Thread的几个常见属性

1. ID 是线程的唯一标识,不同线程不会重复,类似 PID

2. 名称在各种调试工具能用到

3. 状态表示线程当前所处的一个情况

4. 优先级高的线程理论上来说更容易被调度到

5. 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。

Daemon:守护,守护线程 = 后台线程

自己写的代码创建的线程,包括 main 主线程默认都是前台线程,可以通过 setDaemon 方法来修改,但必须在 start 之前修改

例如:

把 t1 这个线程设为了后台线程,它无力阻止进程结束

 6. 是否存活,即简单的理解为run方法是否运行结束了

public class Demo8 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 3; i++) {System.out.println("hello thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 这个结果一定是 false.// 此时还没有调用 start, 没有真正创建线程.System.out.println(t.isAlive());t.start();while (true) {System.out.println(t.isAlive());Thread.sleep(1000);}}
}

线程的入口方法执行结束,系统中对应的线程就随之销毁了,但 t 引用的 Thread 对象仍然存在

 4、启动一个线程 - start()

1. start() 是Java标准库 / JVM提供的方法,本质上是调用操作系统的 API

start 底层代码:

带有 native 的方法,是本地方法,其实现是在JVM内部,通过C++代码来实现的

2. Java 中期望,Thread对象和操作系统中的线程是一一对应的,所以每个Thread对象,都只能 start 一次。每次想创建一个新的线程,都要创建一个新的 Thread 对象(不能重复利用)

3. start 和 run 的区别:run是线程的入口方法,不需要手动调用;start是调用系统 api

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

5、中断(终止)一个线程

让线程的入口方法执行完毕,线程就随之结束了(run 方法尽快 return)

如果把 isFinished 定义成局部变量,是否可以?     不可以

在 lambda 表达式内,希望使用外面的变量,会触发 “变量捕获”


说明:

1. lambda是回调函数,操作系统真正创建出线程之后,才会执行。很有可能后续线程创建好了,当前 main 方法都执行完了,对应的isFinished 就销毁了

2. 为了解决这个问题,Java的做法是,把被捕获的变量给拷贝一份,拷贝到 lambda 内。外面的变量是否销毁,就不影响 lambda 里面的执行了

3. 拷贝,意味着这样的变量不适合进行修改,修改一方,另一方不会随之变化(本质上是两个变量)这种一个变,一个不变,可能给程序员带来困惑

4. 所以,Java的做法是,直接不允许对这个被捕获的变量进行修改


而 isFinished 作为 成员变量时,不是 “变量捕获” 语法,而是成 “内部类访问外部类的成员” 语法,

lambda 本质上是函数式接口,相当于一个内部类。isFinished 变量本身就是外部类的成员,内部类本来就能够访问外部类的成员

成员变量生命周期,是GC(garbage collection 垃圾回收)来管理的。在 lambda 内不用担心变量生命周期失效的问题,也就不必拷贝,不必限制 final 了

currentThread() 方法:

是Java Thread类的一个静态方法,它返回对当前正在执行的线程对象的引用。这个方法在多线程编程中,允许在运行的线程内部访问和控制该线程的状态和行为。

http://www.dtcms.com/a/291613.html

相关文章:

  • Java注解家族--`@ResponseBody`
  • 2025杭电多校赛(2)1006 半
  • 微信二维码扫描登录流程详解
  • Pytorch版本、安装和检验
  • 简单讲解HTTPS如何保证安全性和可靠性
  • 网安学习NO.15
  • 树链剖分-苹果树
  • TPS61194PWPRQ1适用于汽车照明低 EMI、高性能 4 通道 LED 驱动器TPS61194
  • Day07_网络编程20250721_大项目
  • sqli-labs靶场通关笔记:第46-53关 order by注入
  • 一文详解REST风格
  • 青少年科学世界名刊分析评介:《生物技术世界》
  • 机器学习中的数据预处理:从入门到实践
  • Spring 对数组和集合类的自动注入
  • 234、回文链表
  • 使用AI把普通的条形柱状图,丰富成“好看高大上”的条形柱状图
  • 解决win10下Vmware虚拟机在笔记本睡眠唤醒后ssh连接不上的问题
  • PyQt5—QInputDialog 学习笔记
  • 印度给巴铁断水,中国悄然开建雅鲁藏布江水电站,纯属巧合!
  • Python 标准库之 os 模块全面讲解
  • 大模型为什么出现幻觉?
  • 在Anolis8.6上源码编译安装部署OpenVAS(GVM)未完待续
  • 华为云CCE-PV使用OBS存储类之坑
  • Android NDK ffmpeg 音视频开发实战
  • 语义化版本规范(SemVer)
  • 【计算机组成原理】符号位OF、ZF、CF、SF详解
  • c语言 进阶 动态内存管理
  • stream event
  • Playwright-MCP浏览器会话复用全解析
  • swiper js无缝滚动---解决播放总是有间隔、动画一闪一跳的问题