news 2026/6/5 1:13:50

7.多线程深度解析:从Thread基础到线程状态与安全问题

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
7.多线程深度解析:从Thread基础到线程状态与安全问题

目录

一、上节课内容回顾

1. Thread类

2. 线程终止的几种情况

二、本课重点

1. 线程等待:Thread.join

2. 获取当前线程引用

3. 线程休眠

4. 观察线程的所有状态

5. 线程不安全问题

6. 原子性

7. 内存可见性

8. 指令重排序


一、上节课内容回顾

1. Thread类

Thread创建的实例和操作系统中的线程是一一对应的。

创建线程:

  1. 继承Thread,重写run

  2. 实现Runnable,重写run,搭配Thread

  3. 使用匿名内部类,实现Runnable

  4. 使用命名内部类,实现Runnable

  5. lambda

线程属性:​ name、id、前台线程/后台线程

线程的终止/中断:

  • isInterrupted

  • interrupt

public class Demo { public static void main(String[] args) { Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { System.out.println("hello thread"); } }); t.start(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } t.interrupt(); } }

实际开发中,catch中一般不会再次抛出异常:

  1. 重试

  2. 记录日志

  3. 忽略

2. 线程终止的几种情况

  • 立即退出:interrupt()

  • 停止:sleep被唤醒后,发现中断标记,主动退出

  • 不退出:没有break就是不退

main线程调用t.interrupt(),t线程的while循环检测到中断标记,主动退出。

如果catch中没有把异常抛出,而是默默处理(记录日志或忽略),线程会继续执行。


二、本课重点

1. 线程等待:Thread.join

线程之间是随机调度执行的~~

干扰两个线程的结束顺序

后结束的线程,等待先结束的线程执行完~~

public class Demo10 { private static int result = 0; // 预期结果应该是 1000 public static void main(String[] args) { // 在主线程中计算 1+2+...+1000 // 创建新线程,也执行相同的计算逻辑 Thread t = new Thread(() -> { for (int i = 1; i <= 1000; i++) { result += i; } System.out.println("线程计算完成"); }); t.start(); Thread.sleep(1000); System.out.println(result); } }

执行顺序随机了~~

如果t线程的逻辑更复杂,如何评估计算时间呢?

让main线程等待t线程执行完毕~~

public class Demo11 { private static int result = 0; // 预期结果应该是 1000 public static void main(String[] args) { // 获取当前线程的引用 Thread mainThread = Thread.currentThread(); Thread t = new Thread(() -> { for (int i = 1; i <= 1000; i++) { result += i; } System.out.println("线程计算完成"); try { mainThread.join(); } catch (InterruptedException e) { throw new RuntimeException(e); } System.out.println(result); }); t.start(); // 进行计算 for (int i = 1; i <= 1000; i++) { result += i; } } }

如果等的线程一直不退出呢?

死等~~

带有超时时间的等待~~

public class Demo12 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } }); t.start(); t.join(1000); // 最多等 1000ms System.out.println(t.getState()); } }

join 等到超时时间结束~~

只是从阻塞状态还原成就绪的状态~~

继续往下执行还需要等待操作系统的调度

如果调用 join 之前,t 已经执行完了,再次调用 join,此时不会阻塞~~

2. 获取当前线程引用

这个方法我们已经非常熟悉了

public class Demo9 { public static void main(String[] args) { Thread t = new Thread(() -> { Thread cur = Thread.currentThread(); while (!cur.isInterrupted()) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { // throw new RuntimeException(e); // e.printStackTrace(); // 执行一些其他逻辑~~ // 退出之后做一些释放资源类工作 // 没有 break 就是不退出~~ break; } } }); t.start(); } }

注意:

  • 当 lambda 表达式中如果出现 this,this 指向的是外部类的对象

  • 如果使用匿名内部类,this 指向的是外部类的对象

  • 如果使用 lambda,this 指向的是外部类的对象

Thread t = new Thread(new Runnable() { @Override public void run() { // 此处的 run 是 Runnable 的方法,this 指向 Runnable // Runnable 中没有 getName 这样的系列方法 System.out.println(this.getName()); } }); Thread t = new Thread(new Runnable() { @Override public void run() { // 此处的 run 是 Runnable 的方法,this 指向 Runnable //System.out.println(this.getName()); Thread cur = Thread.currentThread(); System.out.println(cur.getName()); } }); Thread t2 = new Thread(() -> { Thread cur = Thread.currentThread(); System.out.println(cur.getName()); }); Thread t2 = new Thread(() -> { System.out.println(this.getName()); // 报错 });

3. 线程休眠

休眠的本质是放弃 CPU 的使用权,把 CPU 让给其他线程执行~~

public class Demo14 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 1; i <= 1000; i++) { System.out.println(i); try { Thread.sleep(1000); // 休眠 1s } catch (InterruptedException e) { e.printStackTrace(); } } }); t.start(); } }

4. 观察线程的所有状态

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

public class ThreadState { public static void main(String[] args) { for (Thread.State state : Thread.State.values()) { System.out.println(state); } } }

Java 给线程引入六种状态 ~~

  • NEW:创建了 Thread 对象,但是还没 start

public class Demo13 { public static void main(String[] args) { Thread t = new Thread(() -> { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(); System.out.println(t.getState()); } }
  • RUNNABLE:一个线程,正在 CPU 上执行

  • WAITING:这几个都表示线程等着其他事情

  • TIMED_WAITING:这几个都表示线程等着其他事情

  • BLOCKED:这几个都表示线程等着其他事情

public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); Thread.sleep(10); System.out.println(t.getState()); t.join(); System.out.println(t.getState()); } }

mainThread: WAITING

mainThread: WAITING

mainThread: WAITING

mainThread: WAITING

......

  • TIMED_WAITING:这几个都表示线程等着其他事情

public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(10000); System.out.println(t.getState()); } }

mainThread: TIMED_WAITING

mainThread: TIMED_WAITING

mainThread: TIMED_WAITING

mainThread: TIMED_WAITING

......

  • BLOCKED:这几个都表示线程等着其他事情(特指由于锁引起的阻塞)

public class Demo13 { public static void main(String[] args) throws InterruptedException { Thread mainThread = Thread.currentThread(); Thread t = new Thread(() -> { while (true) { System.out.println("mainThread: " + mainThread.getState()); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); System.out.println(t.getState()); t.start(); t.join(); System.out.println(t.getState()); } }

5. 线程不安全问题

count++;

这个代码对应了三个 CPU 的指令~~

  1. load 把内存中的数据加载到寄存器中

  2. add 把寄存器中的数据 +1

  3. save 把寄存器中的数据写回到内存里

public class Demo14 { private static int count = 0; // 3 usages public static void main(String[] args) { // 创建两个线程,分别对一个变量进行 5w 次的自增操作 Thread t1 = new Thread(() -> { for (int i = 0; i < 50000; i++) { count++; } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 50000; i++) { count++; } }); t1.start(); t2.start(); // 让主线程等待,等待上述的两个线程结束 t1.join(); t2.join(); System.out.println("count = " + count); } }

预期的结果:10w

实际的结果:小于 10w(有 bug)

调度顺序是不确定的~~

就这两种顺序是正常的

两个线程各自循环 5w 次自增

5w 对操作中,有多少对是有问题的,多少对是没问题的~~~

上述结果,一定是一个 <10w 的值~~~

是否可能会产生 <5w 的值呢? 有可能

多少种情况???

无数种!!!

操作系统对线程的调度是随机的~~

线程安全问题的原因:

  1. [根本原因] 操作系统对于线程的调度是随机的~~(没有办法去应对)

  2. 两个线程针对同一个变量进行修改操作

  • 一个线程针对一个变量进行修改 => 没问题

  • 两个线程针对不同变量进行修改 => 没问题

  • 两个线程针对同一个变量进行读取 => 没问题

不使用多线程 => 单线程 没法充分利用多核 CPU 资源~

不使用多线程,每个线程搞一个变量(比较吃的需求和逻辑的)

相对常见的方案~~(不是 java 中常见)

6. 原子性

修改操作不是原子的~~

count++ 这样的操作是分成了三个 cpu 指令~~

指令是 cpu 上执行的基本单位~~

锁(事务的背后也是和锁密切相关的)

7. 内存可见性

8. 指令重排序

String 属于不可变对象~~

String 不可变对象,是怎么实现??(很多同学的理解是错的)

和 final 无关!!!

禁止你扩展(继承)

没有提供 public 的 set 系列方法~~

为啥要这么设计?

  1. 字符串常量池~~

  2. 计算 hash

  3. 线程安全~~

Java 提供很多种锁的实现,整体的思路类似~~

"互斥""独占"

锁机制,本质上是操作系统提供的功能

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 1:11:21

印刷滚筒平衡机

在印刷行业&#xff0c;印刷滚筒的平衡状况直接影响着印刷品的质量。一个不平衡的印刷滚筒可能会导致印刷图案模糊、色彩不均等问题&#xff0c;严重影响生产效率和产品质量。今天&#xff0c;我们就来深入探讨一下印刷滚筒平衡机&#xff0c;以及上海申帛试验机有限公司在这一…

作者头像 李华
网站建设 2026/6/5 1:11:07

EduCoder实训遇到难题怎么办?除了找答案,这3个高效学习路径更推荐

EduCoder实训高效学习指南&#xff1a;突破困境的3个科学路径当你在EduCoder平台上遇到一道看似无解的编程题时&#xff0c;手指悬停在"搜索答案"按钮上的瞬间&#xff0c;其实隐藏着更重要的学习机会。真正的技术成长往往发生在解决问题的过程中&#xff0c;而非答案…

作者头像 李华
网站建设 2026/6/5 1:10:01

Transformer QKV 计算瓶颈?一次关于长上下文显存爆炸的硬核排查与优化

Transformer QKV 计算瓶颈&#xff1f;一次关于长上下文显存爆炸的硬核排查与优化前言 线上推理延迟突然飙升。显存占用直接爆掉。这是长文本任务的常态。标准 Self-Attention 是罪魁祸首。复杂度是序列长度的平方。当上下文超过 4k tokens。显存压力呈指数级增长。原有方案无法…

作者头像 李华
网站建设 2026/6/5 1:06:05

AI内容工作流会成为品牌基础设施

过去品牌做内容&#xff0c;更多依赖单点工具和人工协作。写脚本有一个工具&#xff0c;剪视频有一个工具&#xff0c;做字幕有一个工具&#xff0c;素材管理又是另一个文件夹。每个环节看起来都能提效&#xff0c;但一旦放到完整内容生产里&#xff0c;问题还是会出现&#xf…

作者头像 李华
网站建设 2026/6/5 1:03:53

AI如何重塑秋冬服装赛道?实现降本增效新突破

换季消费热潮下&#xff0c;秋冬服装赛道竞争愈发激烈&#xff0c;潮流周期缩短、客户审美迭代加快&#xff0c;传统服装运营模式已难以适配当下市场节奏。北京先智先行科技有限公司聚焦实体产业数字化升级&#xff0c;重磅打造三大标杆产品&#xff0c;即“先知大模型”“先行…

作者头像 李华