JavaEE初阶第五期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(三)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

目录

一、线程的状态

1.1. 观察所有线程的状态

1.2. 观察线程的状态和转移

1.3. 线程状态和状态转移的意义

二、线程安全

2.1. 观察线程不安全

2.2. 线程安全的概念

2.3. 线程不安全的原因


一、线程的状态

1.1. 观察所有线程的状态

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

  • NEW:当使用new关键字创建一个线程对象时,它就处于新建状态。此时,线程还没有开始执行,它只是一个Thread类的实例。
  • RUNNABLE:当线程对象调用start()方法后,线程就进入可运行状态。这意味着线程已经准备好运行,正在等待 CPU 调度器分配执行时间。在可运行状态下,线程可能正在运行,也可能正在等待运行。
  • BLOCKED:当一个线程试图获取一个被其他线程持有的锁时,它会进入阻塞状态。一旦锁被释放,线程将重新进入可运行状态,等待调度。
  • WAITING:当线程执行了以下方法时,它会进入等待状态,并且需要其他线程的特定动作才能唤醒。
  • TIMED_WAITING:与等待状态类似,但有时间限制。当线程执行了某些方法时,会进入有时限的等待状态。
  • TERMINATED:当线程的run()方法执行完毕,或者因异常而退出时,线程就进入终止状态。一旦线程进入终止状态,它就不能再被重新启动。

1.2. 观察线程的状态和转移

public class Demo2 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {});// 创建了Thread对象,还没启动System.out.println(t.getState() + " isAlive: " + t.isAlive());t.start();t.join();// 线程执行完了,但Thread对象还在System.out.println(t.getState() + " isAlive: " + t.isAlive());}
}

        通过上面可以看出,操作系统中的线程的生命周期和Thread对象不完全一致。

public class Demo2 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {while (true) {}});// 创建了Thread对象,还没启动System.out.println(t.getState() + " isAlive: " + t.isAlive());t.start();System.out.println(t.getState() + " isAlive: " + t.isAlive());t.join();// 线程执行完了,但Thread对象还在System.out.println(t.getState() + " isAlive: " + t.isAlive());}
}

        因为上面的while循环没有执行完毕,所以t线程一直阻塞,就不会打印第三条语句。只要代码不触发阻塞类操作,就一直是RUNNABLE状态。

1.3. 线程状态和状态转移的意义

        线程状态转移是指线程在执行过程中,从一种状态变为另一种状态。理解线程状态转移对于编写高效、稳定的并发程序至关重要。操作系统需要根据线程状态来合理分配CPU时间、内存等资源。了解这些状态有助于开发者更好地与操作系统协作,避免资源浪费。

二、线程安全

        某一段代码,在单线程环境下是正确的,但放在多线程环境下执行会产生bug,这就是线程安全的问题。

2.1. 观察线程不安全

public class Demo3 {private static int count = 0;public static void main(String[] args) throws InterruptedException {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();// 预期结果是10wSystem.out.println(count);}
}

        但实际运行结果与预期不同,这就是产生了bug,并且每运行一次会产生不同的结果。t1和t2两个线程在同时修改count两个变量,并且修改操作不是“原子的(事务要解决的根本问题)”,就会产生上述问题。count++这样的操作,站在CPU的角度来说,其实是三个指令。对于CPU来说,每个指令是执行的最基本单位,由于操作系统调度线程是随机的,某个线程执行到任意一个指令的时候,都有可能触发CPU调度。
        count++本质上对应三个指令:1.load:把内存中的数值,加载到CPU寄存器里;2.add:把寄存器中的数据进行加1操作,结果还是放到寄存器中;3.save:把寄存器中的值写回到内存中。

        如上图所示,上图的CPU调度就是正确的,可以正确打印出10w,但这毕竟是少数情况,还会出现如下图的情况:

        t1从CPU上切换走的时候,会保存上下文,当再次切换到t1的时候,就会恢复上下文,也就是把CPU寄存器的值全都返回回去,而count虽然执行了两次加1的操作,但打印结果还是1。

2.2. 线程安全的概念

        在并发编程中,线程安全是指当多个线程同时访问或操作同一个数据或资源时,程序能够正确地执行,并且不会产生不正确的结果或不可预测的行为。简单来说,就是你的代码在多线程环境下也能正常工作,不会因为多个线程的并发执行而“出错”。

2.3. 线程不安全的原因

  • 执行式抢占

        线程的随机调度这是造成线程安全问题的罪魁祸首。写代码的时候,需要确保无数种调度顺序下,总体的结果都是正确的。

  • 多个线程同时修改一个变量
public class Demo3 {//    private static int count = 0;public static void main(String[] args) throws InterruptedException {int count = 0;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();// 预期结果是10wSystem.out.println(count);}
}

        如果我们把上面的代码中的静态成员变量修改为局部变量,会出现报错,原因如下图所示:

        在Lambda表达式中对局部变量进行捕获,这个变量要么是被final修饰的,如果不是被final修饰的,要确保在使用之前没有被修改。

        注意,如果是单个线程修改一个变量,多个线程访问同一个变量,多个线程修改不用变量是没事的。

  • 修改操作不是“原子性”的

        原子性指一个操作或一组操作在执行过程中不可被中断,要么全部完成,要么完全不执行,不存在中间状态。在并发编程中,原子性是保证数据一致性的基础,避免多线程竞争共享资源时出现 “部分执行” 的错误。

        例如,count++看似单一操作,实则分为三步(读取值 → 加 1 → 写入值)。若线程 A 读取count=0后被中断,线程 B 读取count=0并完成写入count=1,此时线程 A 恢复执行并写入count=1,最终结果应为2却得到1,这就是原子性缺失导致的错误。

  • 指令重排序

        为了提高执行效率,处理器和编译器可能会对指令进行重排序。在单线程环境下,这种重排序不会影响程序的最终结果。但在多线程环境下,指令重排序可能会破坏程序的逻辑顺序,导致意想不到的结果。

  • 内存可见性

        在多核处理器系统中,每个处理器都有自己的高速缓存。为了提高性能,线程可能会将共享变量的值缓存到自己的本地缓存中。如果一个线程修改了共享变量的值,但这个修改还没有及时地刷新到主内存,那么其他线程从主内存或者自己的本地缓存中读取到的仍然是旧的值。

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

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

相关文章

Python爬虫实战:研究xmltodict库相关技术

1. 引言 1.1 研究背景与意义 气象数据在农业生产、交通规划、灾害预警等多个领域具有重要应用价值。传统的气象数据获取方式主要依赖于气象部门发布的统计信息,存在更新不及时、数据维度有限等问题。随着互联网技术的发展,气象网站提供了丰富的实时气象数据,但这些数据通常…

暴力风扇方案介绍

炎炎夏日&#xff0c;当普通风扇只能送来 “温柔拂面”&#xff0c;暴力风扇却能吹出 “台风级” 清凉&#xff01;想知道这些 “风力狂魔” 是如何炼成的&#xff1f;答案藏在电机、电路和芯片的黄金三角组合里。​ 一、电机&#xff1a;暴力风扇的 “心脏起搏器”​ 暴力风扇…

电子计数跳绳原型

引子 这些日子和跳绳杠上了&#xff0c;前些日子的拆解&#xff0c;让我有一个大胆的想法&#xff0c;就是能不能用3D打印写程序的方式实现一个计数功能的&#xff0c;当然看到有些结构这个现在实现起来有些难度&#xff0c;只能那原有的部件充数。目标只是说明一下原理&#…

桌面小屏幕实战课程:DesktopScreen 17 HTTPS

飞书文档http://https://x509p6c8to.feishu.cn/docx/doxcn8qjiNXmw2r3vBEdc7XCBCh 源码参考&#xff1a; /home/kemp/work/esp/esp-idf/examples/protocols/https_request 源码下载方式参考&#xff1a; 源码下载方式 获取网站ca证书 openssl s_client -showcerts -connec…

Spring Cloud:高级特性与最佳实践

一、Spring Cloud 高级特性 &#xff08;一&#xff09;分布式配置管理 分布式配置管理是微服务架构中的一个重要功能&#xff0c;Spring Cloud 提供了多种配置管理解决方案&#xff0c;如 Spring Cloud Config 和 Spring Cloud Consul。 Spring Cloud Config Spring Cloud C…

理解图像的随机噪声

图像灰度信息很难精确测量&#xff0c;一般情况下测量值总在真实值附近晃动&#xff0c;使用概率模型可以对该随机性建模&#xff0c;大致如下&#xff1a; 1 概率密度函数 1&#xff09;随机变量 x 的概率密度函数 p(x) 定义为&#xff1a;当 趋近于 0 时&#xff0c;在区间 上…

spring中maven缺少包如何重新加载,报错java: 程序包org.springframework.web.reactive.function不存在

错误原因分析 java: 程序包org.springframework.web.reactive.function不存在 这个错误是由于 项目中缺少 Spring WebFlux 相关依赖 导致的。org.springframework.web.reactive.function 包属于 Spring WebFlux 模块&#xff08;用于响应式 Web 开发&#xff09;&#xff0c;如…

【C++】atoi和std::stoi

两个将字符串转为int的方法 atoi&#xff08;C语言&#xff09; atoi 是 C 库中的一个函数&#xff0c;它定义在 <cstdlib> 头文件里。其作用是把一个字符串转换为对应的整数。 /* Convert a string to an integer. */ extern int atoi (const char *__nptr)__THROW …