【JavaEE-多线程背景-线程等待-线程的六种状态-线程安全问题-详解】

🌈个人主页:SKY-30
个人推荐:基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解
学好数据结构,刷题刻不容缓:点击一起刷题
🌙心灵鸡汤总有人要赢,为什么不能是我呢
在这里插入图片描述

在这里插入图片描述

🌈🌈🌈Java的多线程

⚡⚡⚡多线程的背景

为了充分利用cpu的多核心特性,我们引进了一种新的编程形式,并发编程,当然进程也可以进行并发编程,但是进程太"重"了,我们每次创建一个进程需要耗费很大的资源,对于有些业务场景可能需要对进程频繁的创建和删除,所以我们又引进了一个更轻量的进程-线程.

在这里插入图片描述

⚡⚡⚡多线程和多进程的联系

线程包含在进程之中,一个进程中至少包含一个或多个线程,对于线程来说它比进程创建和销毁的成本更小,并且一个进程中的多个线程是公用一份系统资源的(硬盘,网络带宽,内存…).

进程是操作系统资源分配的基本单位

线程是操作系统调度执行的基本单位

在这里插入图片描述

🌈🌈🌈在Java中使用多线程

==在Java中我们使用Java标准库中的Thread对象实现多线程.==对于实现Java的多线程我们这里有多种方式,这里给大家全部列举出来.

🌈🌈🌈创建线程

⚡⚡⚡⚡实现Thread类

我们可以实现一个继承了Thread类的类,然后重写run方法,在run方法中完成多线程的代码逻辑.然后再main方法中实例化对象,启动线程.

// 定义一个Thread类,相当于一个线程的模板
class MyThread01 extends Thread {// 重写run方法// run方法描述的是线程要执行的具体任务@Overridepublic void run() {System.out.println("hello, thread.");}
}/*** 继承Thread类并重写run方法创建一个线程** */
public class Thread_demo01 {public static void main(String[] args) {// 实例化一个线程对象MyThread01 t = new MyThread01();// 真正的去申请系统线程,参与CPU调度t.start();}
}

⚡⚡⚡实现Runnable接口

这里我们也可以通过实现Runnable接口,然后实现run方法,实现多线程.然后将Runnable以参数的形式传递给Thread类,完成线程的创建.

// 创建一个Runnable的实现类,并实现run方法
// Runnable主要描述的是线程的任务
class MyRunnable01 implements Runnable {@Overridepublic void run() {System.out.println("hello, thread.");}
}
/*** 通过继承Runnable接口并实现run方法* * */
public class Thread_demo02 {public static void main(String[] args) {// 实例化Runnable对象MyRunnable01 runnable01 = new MyRunnable01();// 实例化线程对象并绑定任务Thread t = new Thread(runnable01);// 真正的去申请系统线程参与CPU调度t.start();}
}

⚡⚡⚡使用匿名内部类实现线程

由于我们上面的几种创建方式,都是利用继承和接口的形式,通过匿名内部类的学习,我们很容易想到我们完全可以利用匿名内部类实现一个线程,大大简化了代码的复杂性,是一种比较流行的形式.

/*** 通过Thread匿名内部类的方法创建一个线程* **/
public class Thread_demo03 {public static void main(String[] args) {Thread t = new Thread(){// 指定线程任务@Overridepublic void run() {System.out.println(Thread.currentThread().getName());}};// 真正的申请系统线程参与CPU调度t.start();}
}

⚡⚡⚡通过Runnable匿名内部类创建一个线程

*** 通过Runnable匿名内部类创建一个线程* **/
public class Thread_demo04 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {// 指定线程的任务@Overridepublic void run() {System.out.println(Thread.currentThread().getName());                }});// 申请系统线程参与CPU调度t.start();}
}

⚡⚡⚡利用lambda表达式

不知道大家是否记得.lambda表达式的使用场景,就是这个类只用一次,并且这里必须是函数式接口,才可以使用,这里我们的Runnable就是函数式接口

在这里插入图片描述
所以这里我们也可以使用lambda表达式实现一个线程

/*** 通过Lambda表达式的方式创建一个线程*/
public class Thread_demo05 {public static void main(String[] args) {Thread t = new Thread(() -> {// 指定任务:任务是循环打印当前线程名while (true) {System.out.println(Thread.currentThread().getName());try {// 休眠1000msThread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});// 申请系统线程参与CPU调度t.start();}
}

🌈🌈🌈Java中多线程的重要属性

在这里插入图片描述
这里强调一下==isDaemon()==方法,可以判定是否为后台线程即后台线程不会影响进程的运行,而前台线程结束时会关闭当前的进程的运行.

还有就是getState()获取当前线程的状态,一种有六种状态,这个我们后面再说

🌈🌈🌈启动线程

我们上面说了,可以以来Thread即java的标准库中的类来实现一个线程对象,但是,想要真正启动一个线程还是远远不够的,我们还需要依赖一个方法start(),来启动线程,只有调用了start这个方法,线程才会真正运行,这里还经常会有哦一个面试题:

run方法和start方法有什么区别???

run方法:它是线程的入口,在run方法内部我们完成线程内部要执行的代码,相当于线程的主题内容
start方法:它是线程真正启动的标志,只有当前对象调用了start方法,线程才会真正运行.

特别说明:一个线程,只能调用一次start方法

🌈🌈🌈线程终止

对于一个线程来说,要想终止,只能加快run方法的执行,将run方法结束,来结束线程.这里我们利用interrupt方法终止线程,并且这个方法还会设置一个标志位.

public class ThreadInterruptExample {public static void main(String[] args) {Thread thread = new Thread(() -> {try {while (!Thread.currentThread().isInterrupted()) {System.out.println("Thread is running...");Thread.sleep(1000); // 模拟一些工作}} catch (InterruptedException e) {System.out.println("Thread was interrupted!");}System.out.println("Thread is exiting.");});thread.start(); // 启动线程try {Thread.sleep(2500); // 等待2.5秒} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt(); // 请求中断线程}
}

这里就是在main方法中通过interrupt方法终止线程thread,然后开始执行main中的剩余代码.

特别说明interrupt方法执行之后,还会将sleep的线程唤醒,并且清空标志位

public class ThreadInterruptExample {public static void main(String[] args) {Thread thread = new Thread(() -> {try {while (true) {System.out.println("Thread is running...");Thread.sleep(1000); // 模拟阻塞状态}} catch (InterruptedException e) {System.out.println("Thread was interrupted!");return; // 退出线程}});thread.start(); // 启动线程try {Thread.sleep(2500); // 主线程等待2.5秒} catch (InterruptedException e) {e.printStackTrace();}thread.interrupt(); // 请求中断线程}
}

我们创建了一个线程,该线程在无限循环中运行,并每1秒打印一次消息。
线程在循环中调用sleep(1000),模拟阻塞状态。
在主线程中,我们让线程运行2.5秒。
然后,我们调用线程的interrupt()方法,这会请求中断线程。
当线程的sleep()方法被中断时,会抛出InterruptedException。这个异常会清除线程的中断状态,并且线程会退出。

⚡⚡⚡线程等待

对于多个线程来货,CPU遵循的原则是随机调度,抢占式执行.,然后后面的线程就会进入我们无法控制线程的执行,但是我们控制线程的结束顺序,即我们让后结束的线程等待先结束的线程,这里把将后结束的线称为等待状态,我们一般叫做-阻塞状态.这种阻塞状态直到被等待线程执行完毕之后才能开始执行.

那么我们如何控制线程的结束顺序呢,一般通过join这个方法来控制,比如说现在有两个线程,a,b我们在a线程中调用一个b.join()此时就会使b线程先执行,a线程陷入阻塞状态,直到b执行完毕,a才能开始执行.

public class Main {public static void main(String[] args) {Thread worker = new Thread(new Runnable() {public void run() {System.out.println("Worker thread is running.");try {Thread.sleep(2000); // 模拟工作线程执行任务需要2秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Worker thread has finished.");}});worker.start(); // 启动工作线程try {worker.join(); // 主线程等待工作线程完成} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main thread is continuing after worker thread has finished.");}
}

这里的join就是主线程在等待worker执行完毕之后,主线程才开始执行,主线程在这个过程中处于阻塞状态.

⚡⚡⚡join()方法

对于这个方法来说,它可以决定线程的等待状态,然而,如果被等待线程如果出现了一些bug,那么等待线程就会一直处于等待状态,即"死等状态",但是显然这种状态并不是我们想要的,所以join方法就给我们提供了两种版本,有参数和无参数版本,无参数即可能陷入死等状态,有参数即可以设置等待的时间单位为毫秒,时间结束不管被等待线程有没有执行完毕,都会结束这个join方法.

🌈🌈🌈线程的状态

对于进程来说,大致分为两种状态,就绪状态和阻塞状态.
就绪状态:正在运行,或者随时可以调到CPU上执行.
阻塞状态:线程处于等待状态,不能立刻执行

然而对于线程来说,他的状态就远远不止这两种了~~
线程一共有六种状态,这里给大家列举出来

NEW

此时线程的Thread对象已经创建完成,但是还没有启动线程,即没有调用start方法,此时的状态称为NEW,可以利用getState方法获取线程的状态

public class Test {public static void main(String[] args) {Thread worker = new Thread(new Runnable() {public void run() {System.out.println("Worker thread is running.");try {Thread.sleep(2000); // 模拟工作线程执行任务需要2秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Worker thread has finished.");}});System.out.println( worker.getState());worker.start(); // 启动工作线程try {worker.join(); // 主线程等待工作线程完成} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main thread is continuing after worker thread has finished.");}
}

运行结果:
在这里插入图片描述

⚡⚡⚡TERMINATED

此时的线程已将结束了,但是Thread类对象还没有销毁…

public class Main {public static void main(String[] args) {Thread worker = new Thread(new Runnable() {public void run() {System.out.println("Worker thread is running.");try {Thread.sleep(2000); // 模拟耗时操作} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 重新设置中断状态}System.out.println("Worker thread has finished.");}});worker.start(); // 启动线程while (!worker.getState().equals(Thread.State.TERMINATED)) {System.out.println("Main thread is waiting for worker thread to finish.");try {Thread.sleep(1000); // 每1秒检查一次} catch (InterruptedException e) {Thread.currentThread().interrupt(); // 重新设置中断状态}}System.out.println("Worker thread is in TERMINATED state.");}
}

⚡⚡⚡BLOCKED

这是由于锁竞争导致的阻塞,这里我们还没有引入锁所以先跳过

⚡⚡⚡RUNNABLE

就绪状态:正在运行或者随时可以调度到CPU执行

public class Test {public static void main(String[] args) {Thread worker = new Thread(new Runnable() {public void run() {System.out.println( Thread.currentThread().getState());System.out.println("Worker thread is running.");try {Thread.sleep(2000); // 模拟工作线程执行任务需要2秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Worker thread has finished.");}});//System.out.println( worker.getState());worker.start(); // 启动工作线程try {worker.join(); // 主线程等待工作线程完成} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main thread is continuing after worker thread has finished.");}
}

运行结果
在这里插入图片描述

⚡⚡⚡TIMED_WAITING和WAITING

这两种状态其实就是我们上面提到的一个是处于有时间的暂时等待,另一个则是无限制的死等,即可以利用join的两个版本实现这两个状态.对于有时间的,也可以用sleep检验…

🌈🌈🌈🌈线程安全

在多线程的概念中,线程安全是非常重要的一个知识点,一定要认真巩固,那么线程安全从何而来呢,这里就是因为多线程编程的底层原理,随机调度,抢占式执行,在这样的机制下就会导致线程安全的问题,为了让大家了解的更清楚,给大家举一个例子.

public class MyThreadDemo1 {public 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();System.out.println(count);}
}

这里运行出来的结果就并非是100000,原因是内存中的count值没有及时更新

这里的count++其实不仅仅是一步操作,对于CPU来说其实一共有三步操作:

  • load:将数据从内存中加载到寄存器中
  • add:寄存器中的数值执行++操作
  • save:保存sount的值并更新内存中的count的值

然而对于多线程来说,随时都可能被其他线程占据cpu的运行资源,由于cpu的最小执行单位是指令,所以起码每次执行终止都会让当前的指令执行完毕.
这样的机制就会导致线程安全问题,下面给大家画图演示一下.
在这里插入图片描述
此时就会出现t2虽然count值已经加一,但是没有及时更新内存(方块)中的count值,所以导致后面t1加载count时还是0所以后面的count的值就是1

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

高性能、安全、低碳绿色的趋势下,锐捷网络发布三擎云办公解决方案 3.0

桌面虚拟化作为云时代的主流和热门技术&#xff0c;已经取得了广泛应用。随着生成式 AI 爆炸式发展&#xff0c;CSDN 看到&#xff0c;人工智能正在引发计算、开发、交互三大范式的全面升级&#xff0c;技术开发或将迎来一次全新的科技变革周期&#xff0c;因此 VDI 云桌面随之…

20分钟迁移完阿里云ECS跨区域迁移,用老操作系统作为新服务操作系统

由于特殊原因或者数据备份需要迁移ecs服务器 跨区域复制 镜像复制 由于特殊原因或者数据备份需要迁移ecs服务器 1.老服务快照 选择ecs实例&#xff0c;点开实例 进入云盘 https://ecs.console.aliyun.com/disk 在云盘上点击建立快照 https://oss.console.aliyun.com/bu…

C# 位移运算符 <<、>>, 以及 operator 关键字 重载运算符

关键字 operator operator关键字用于在类或结构声明中声明运算符。运算符声明可以采用下列四种形式之一&#xff1a; public static result-type operator unary-operator ( op-type operand ) public static result-type operator binary-operator ( op-type operand, op-ty…

IO、进程、线程03

第一题&#xff1a;预习 opendir 和 readdir函数 opendir 和 readdir 是两个在C语言&#xff08;特别是使用POSIX标准的系统&#xff0c;如Linux和UNIX&#xff09;中用于目录遍历的函数。这两个函数属于标准的C库中的目录操作部分&#xff0c;通常与<dirent.h>头文件一…

国内新能源汽车芯片自给,承认差距,任重道远

【科技明说 &#xff5c; 科技热点关注】 据近日工信部电子五所元器件与材料研究院高级副院长罗道军表示&#xff0c;中国拥有最大的新能源车产能&#xff0c;芯片用量也是越来越多。但是芯片的自给率目前不到10%&#xff0c;是结构性的短缺。 中国拥有最大新能源车产能&#…

万界星空科技MES系统车间设备管理模块的功能

MES系统&#xff08;制造执行系统&#xff09;中的车间设备管理模块是生产环境中不可或缺的一部分&#xff0c;它负责监控、管理和优化车间内所有设备的性能、利用率和维护活动。以下是该模块详细的功能描述&#xff1a; 1. 设备状态监控与实时数据采集 实时监控&#xff1a;通…

【Node.js】会话控制

express 中操作 cookie cookie 是保存在浏览器端的一小块数据。 cookie 是按照域名划分保存的。 浏览器向服务器发送请求时&#xff0c;会自动将 当前域名下可用的 cookie 设置在请求头中&#xff0c;然后传递给服务器。 这个请求头的名字也叫 cookie &#xff0c;所以将 c…

cs224w笔记(p1-p4)

视频b站&#xff1a;1.1 - Why Graphs字幕版gamma_哔哩哔哩_bilibili p4前是数据结构基本内容主要涉及图的基本知识&#xff08;略&#xff09;。下面是未在考研数据结构范围内的知识。 节点中心性是图论和网络分析中用来衡量图中节点重要性的一个概念。 包括&#xff1a;度中…

解决:Linux上SVN 1.12版本以上无法直接存储明文密码

问题&#xff1a;今天在Linux机器上安装了SVN&#xff0c;作为客户端使用&#xff0c;首次执行SVN相关操作&#xff0c;输入账号密码信息后&#xff0c;后面再执行SVN相关操作&#xff08;比如"svn update"&#xff09;还是每次都需要输入密码。 回想以前在首次输入…

基于springboot+vue+uniapp的高校就业招聘系统小程序

开发语言&#xff1a;Java框架&#xff1a;springbootuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#…

Grafana :利用Explore方式实现多条件查询

背景 日志统一推送到Grafana上管理。所以&#xff0c;有了在Grafana上进行日志搜索的需求&#xff0c;而进行日志搜索通常需要多条件组合。 解决方案 通过Grafana的Explore的方式实现多条件查询。 直接看操作步骤&#xff1a; 在主页搜索框中输入“Explore” 进入这个界面…

llama 2 改进之 RMSNorm

RMSNorm 论文&#xff1a;https://openreview.net/pdf?idSygkZ3MTJE Github&#xff1a;https://github.com/bzhangGo/rmsnorm?tabreadme-ov-file 论文假设LayerNorm中的重新居中不变性是可有可无的&#xff0c;并提出了均方根层归一化(RMSNorm)。RMSNorm根据均方根(RMS)将…

昇思25天学习打卡营第18天|RNN实现情感分类

相关知识 情感分类 指输入一段话或句子&#xff0c;返回该段话的正向或复兴的情感分类。 text embedding 指将文本转化成向量的方法。这里的文本指词、句子、文档等文本序列。 词向量化后会将词转为二进制(独热编码)或高维实数向量&#xff0c;句子和文档向量化则将句子或文…

给Wordpress评论列表的用户昵称增加个性化角色称号和注册年数

什么是个性化角色称号? 个性化称号:其实就是对应wordpress的几个用户组,重新给它装个面具。 比如:管理员 -> 华山掌门 比如:订阅者 -> 华山弟子 比如:VIP组 -> 掌门亲传弟子 。。。 就是个好玩的东西 什么又是注册年数? 显示用户在你的网站上注册了多少…

spring ioc的原理

1、控制反转(IOC):对象的创建控制权由程序自身转移到外部&#xff08;容器&#xff09; 2、依赖注入(DI):所谓依赖注入&#xff0c;就是由IOC容器在运行期间&#xff0c;动态地将某种依赖关系注入到对象之中。 Spring 中的 IoC 的实现原理就是工厂模式加反射机制。 参考资料…

电脑屏幕录制怎么弄?分享3个简单的电脑录屏方法

在信息爆炸的时代&#xff0c;屏幕上的每一个画面都可能成为我们生活中不可或缺的记忆。作为一名年轻男性&#xff0c;我对于录屏软件的需求可以说是既挑剔又实际。今天&#xff0c;我就为大家分享一下我近期体验的三款录屏软件&#xff1a;福昕录屏大师、转转大师录屏大师和OB…

Chromium CI/CD 之Jenkins实用指南2024 - 常见的构建错误(六)

1. 引言 在前一篇《Chromium CI/CD 之 Jenkins - 发送任务到Ubuntu&#xff08;五&#xff09;》中&#xff0c;我们详细讲解了如何将Jenkins任务发送到Ubuntu节点执行&#xff0c;并成功验证了文件的传输和回传。这些操作帮助您充分利用远程节点资源&#xff0c;提升了构建和…

浅谈安数云智能安全运营管理平台:DCS-SOAR

SOAR&#xff08;security orchestration&#xff0c;automation and response&#xff09;&#xff0c;由Gartner于2015年提出&#xff0c;最初的含义是安全运营、分析与报告。2017年&#xff0c;Gartner又重新定义了SOAR的能力&#xff0c;包括安全编排、安全自动化和安全响应…

STM32CubeIDE(STM32L432KC片上ADC)

目录 一、概念 二、使用 1、定时器触发模式 1.1 软件配置 1.2 代码编写 2、定时器触发ADC 多通道DMA 2.1 软件配置 2.2 代码编写 一、概念 1. 类型与精度&#xff1a;STM32L432KC的ADC是一个12位逐次逼近型模拟数字转换器&#xff0c;能够提供高精度的模拟信号测量。其…

OPC UA边缘计算耦合器BL205工业通信的最佳解决方案

OPC UA耦合器BL205是钡铼技术基于下一代工业互联网技术推出的分布式、可插拔、结构紧凑、可编程的IO系统&#xff0c;可直接接入SCADA、MES、MOM、ERP等IT系统&#xff0c;无缝链接OT与IT层&#xff0c;是工业互联网、工业4.0、智能制造、数字化转型解决方案中IO系统最佳方案。…