JavaEE 多线程详细讲解(1)

1.线程是什么

(shift + F6)改类名

1.1.并发编程是什么

(1)当前的CPU,都是多核心CPU

(2)需要一些特定的编程技巧,把要完成的仍无,拆解成多个部分,并且分别让他们在不同的cpu上运行。

       要不然就会编程,一核有难,多核围观的场景

      并发编程指的是(并行+并发)

(3)通过多进程编程的模式就可以达到并发编程的效果。  

       因为进程可以被调度到不同的cpu上面运行

      此时就可以把多个cpu核心核心都很好的利用起来

 (4)虽然多进程编程可以解决上述问题,但是带来了新的问题。

1.2.问题?

(1)在编程过程中,服务器开发的圈子李,这种并发编程要能够给多个客户端提供服务。

如果同一时间来了很多客户端,服务器如果只利用一个cpu核心工作,速度会比较慢

(2)多搞几个厨师,来炒菜,这样就大幅的提高了效率

(3)一个比较好的做法就是每个客户端连上服务器,服务器都创建一个新的进程,给客户端提供服务,这个客户端断开了,服务器再把进程示范掉

如果服务器,频繁有客户端来来去去,服务器就需要频繁创建销毁进程。(服务器响应速度会变慢)

1.3.如何解决这个问题?线程的引出

引入线程,主要的出现,就是为了解决上述进程,太重量的问题

(1)线程(thread)也叫做轻量级进程(创建销毁的开销更小)。

(2)线程可以理解为进程的一部分,一个进程中可以包含一个线程或者多个线程

(3)描述进程使用的是PCB这样的结构体,事实上更严格的说,一个PCB其实是描述一个进程的,若干个PCB联合在一起,是描述一个进程的。

(4)pcb中包括(pid 内存指针 文件描述表 状态上下文,优先级,记账信息 tgid)每一个线程所提供的pid是不一样的但是tgit是一样的,所以我们可以用tgit来判断这个线程是不是在一个线程下面

(5)还要同一个进程的若干个线程,这里的内存指针和文件描述表,其实是同一个。 

(6)状态上下文优先级记账信息每个线程有一组自己的属性

(7)同一个进程中的若干个线程之间,是相同的内存资源和文件资源的。

          线程之间可以互相访问

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

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

同一个进程包括N个线程,线程之间是共用资源

只有你创建第一个线程的时候,去进行资源的申请操作,后序再创建线程,都没有申请资源的过程了。

1.3.1并发编程

(1)在我们引入了进程以后,那么我们就可以思考,进程的作用是什么?

(2)图解举例

 (3)我们现在的计算机不都是好多核心,所以我们可以用多个线程来对工作进行拆分

(4)由于成本很高,而且电脑的核心也并不是特别的多,所以我们引出了多线程编程。

         相当于线程为进程分担了问题

(5)这时候就有同学提出问题,是不是线程越多越好,这当然不是,因为线程的调度也是需要分配资源的,合适的线程可以帮助你来更好的管理资源,但是如果线程过多的化那么线程就会变慢。

(6)其中我们还要思考一个问题就是线程的调度资源到底花费到哪里

(7)其中多核CPU才能进行这种操作

 (8)其中,在线程不是太多的情况下,也可能发生线程安全的问题。

1.4总结

总结:为啥说线程更加轻量,开销更小,核心就在于,创建进程可能包含多个线程,这个过程中,涉及到资源分配,资源释放。

1.5线程于进程的关系以及相关问题

1.6关键面试题(线程和进程的区
别)

2.代码实现线程和进程、

线程本身是操作系统提供的。

操作系统提供了API让我们操作系统

JVM就对操作系统的api进行封装

线程这里提供了thread类实现了封装

2.1线程的代码实现

其中,操作系统提供了api来对线程进行操作,然而我们的编译器,使用了JVM(java虚拟机)来进行操作,操作系统的api,其中,在java中我们使用的就是Thread类来对线程进行操作

2.1.1Thread 代码的基本实现

其中,run方法是Thread中本来就有的方法 ,我们把他重写了

进入源代码

可以看到我们重写了run方法。

2.1.1.1注意事项

(1)上面的这段代码其实是创建了两个线程,分别是main线程,另一个就是t线程

(2)其中main是主线程,也就是jvm在运行的时候就会创建出来。

(3)其中一个线程中至少包含一个进程,然后再进程中,至少包含一个线程,在没有引入多并发编程之前,我们运行的程序都是一个进程,然后我们在一个线程下面编写代码。

(4)在上面的程序中就是,一个进程,然后进程的下面有两个线程,分别是t线程,然后是jvm生成的主线程mian

2.1.2线程的状况(线程一直进行来进行分析)

其中try catch来对其进行捕获,是因为防止在其他线程或者什么东东调用正在休眠的线程,这时候就会一直等下去,也就是阻塞,所以我们要用trycatch来进行处理

try {// 尝试执行这里的代码,可能会抛出异常
} catch (ExceptionType1 e) {// 如果try块中的代码抛出了ExceptionType1类型的异常,则执行这里的代码
} catch (ExceptionType2 e) {// 如果try块中的代码抛出了ExceptionType2类型的异常,则执行这里的代码
} finally {// 无论是否发生异常,finally块中的代码总是会被执行
}

代码整体实现

package thread;
class MyThread extends Thread{public void run(){while(true){System.out.println("HelloThread1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}
public class Demo1 {public static void main(String[] args) {Thread t = new MyThread();t.start();while(true){System.out.println("HelloThread1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

 其中main和t线程是并发执行的

其中我们要注意的一点就是sleep后面的休眠时间的单位是ms,其中sleep的作用就是将这个停一下在方到系统上面的CPU上面进行执行

时间到了以后就会解除阻塞状态也就是。

它可以让我们的CPU的调度有休息可以更好的处理资源的调度

2.1.3线程的使用情况(2.1.2中的代码)

在运行的时候我们可以看到每秒钟打印一次

其中main可能t线程的前面

t线程也可能在main线程的后面

也就是谁抢上资源谁就进行输出

2.1.4其他一些关系线程的东西就一起来说明了

(1)

 总的来说就是,我们定义了一个线程的基本框架(MyThread类),其中在main方法中的Thread t = new MyThread只是实例化这个对象而已,也没有创建线程,这时候的start是Thread中里面的方法,只有在调用这个方法的时候,才会使用我们实例化的线程框架,来创建线程

通俗点说就是

(2)其中要注意的是

 所以刚才写的代码就是线程的串行执行

 2.2Thread的五种写法

2.2.1第一种创建一个类继承Thread方法

这个是我们一开始说的那个方法

2.2.2第二种创建一个类描述线程,实现Thread的方法 

这个方法实现线程的方式是比较好的方式

它能大大的降低耦合度

我们不同于第一种创建,我们是实例化Thread然后把MyRunnable作为参数传入实例化的对象中作为参数

两种Thread的对比(1)和(2)种方法

2.2.3第三种创建 使用匿名内部类创建线程框架(继承Thread类)

代码实现

这个定义了一个匿名内部类来对,这个匿名内部类直接继承Thread类然后重写Thread的中的run方法,然后最后t.star就可以了

总结

 2.2.4第四种创建方式lambod表达式创建(JDK8的新属性)

这个方式可以理解为用函数式接口的方法

在Thread类中式没有抽象方法的,但是在Thread中实现了Runnable接口,里面有一个run抽象方法,lambod表达式就会重写这个方法来做出线程的框架

相较于Thread的内部类来实现来说 lambad表达式只能用函数式接口来重写run,但是比内部类实现来说方便许多,匿名内部类实现的化比它麻烦一点,但是它的扩展性比lamda表达式要好很多

注意的一点就是主线程一直在其他线程完成以后再执行的

代码实现

2.2.5第五种方法是实现Runnable重写run使用内部类

总结

 

 2.3线程的几种其他用法

2.3.1给线程定义名字

(1)在线程的结尾来定义名字

代码示例

package thread;public class Demo6{public static void main(String[] args) {Thread t = new Thread(()->{while (true){System.out.println("make name from Thread");try {Thread.sleep(10000);} catch (InterruptedException e) {e.printStackTrace();}}},"Frank");t.start();}
}

 2.4Catch中写的是什么?Sleep的注意事项

(1)

 (2)

其中我们要注意的就是在main方法中我们有两种方法来处理sleep

分别时 :try catch 和Throws

用try catch 可以捕获异常然后程序员选则如何处理这个异常

用Throws是捕获的异常必须要优先处理

2.5小结

3线程的区别 

线程分为分为前台线程和后台线程

前台线程就是这个线程不结束java程序

后台线程就是这前台线程结束那么他就结束了

4将进程设置为后台进程

代码实现

public class DaemonThreadExample {public static void main(String[] args) {// 创建一个线程Thread daemonThread = new Thread(new Runnable() {@Overridepublic void run() {while (true) {try {System.out.println("Daemon Thread is running...");Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}});// 将线程设置为后台线程daemonThread.setDaemon(true);// 启动线程daemonThread.start();// 主线程执行完毕,由于daemonThread是后台线程,程序不会等待它执行完毕就会退出System.out.println("Main thread is exiting...");}
}

5.其他知识的扩充

(1)

6.如何让线程中断 

我们可以定义一个布尔值的变量其中,我们可以用布尔值来控制循环的开始和结束。

代码实现

其中要注意的是外部类成员是不会报错

(2)线程的中断

总结一下,`t`线程在`sleep`状态时,就像你的朋友决定去睡觉并且不想被打扰一样,它是不会响应中断请求的。如果你想让它在需要的时候提前醒来,你需要在编写代码时,让它能够检查并响应中断请求。

6.1线程如何更好的中断

代码解析

首先,我们要明白什么是线程中断。在Java中,线程中断是一种协作机制,允许一个线程请求另一个线程停止它正在做的事情。这通常是因为某种原因,比如用户取消了操作,或者系统需要释放资源。

Thread.currentThread().isInterrupted()
这个方法用来检查当前线程是否已经被其他线程中断。中断状态是一个标志,当一个线程调用另一个线程的
interrupt()
方法时,它会设置这个标志。

while (!Thread.currentThread().isInterrupted())
这个循环的意思是:“只要当前线程还没有被中断,就继续执行循环里的代码。” 如果线程在循环内部被中断(比如其他线程调用了它的
interrupt()
方法),那么
isInterrupted()
方法会返回
true
,循环条件变为
false
,循环就会停止。

现在,让我们看一个简单的例子:

假设你正在煮一壶水,并且你想在水开之前不断检查它。你可以把煮水的过程想象成一个线程的任务。如果突然有急事要离开,你可能会想中断这个线程(也就是停止检查水是否开了)。

在这个例子中,
while (!Thread.currentThread().isInterrupted())
就像是你在不断地问自己:“我是不是应该停止检查水是否开了?” 只要你没有被中断(也就是没有急事发生),你就会继续检查。一旦你收到中断信号(比如有人叫你),你就会停止检查并去处理其他事情。

Thread.interrupt()
方法就像是有人按下了停止按钮,告诉线程:“你应该停止你正在做的事情了。” 而
isInterrupted()
方法就像是线程在回答:“好的,我已经收到停止的信号了。”

这时候当我们进行运行的时候会发现虽然我们返回了true想要中断,但是却没有中断,这是因为sleep会将false再修改回true,这是为了能够让程序员更好的控制线程而不是让程序自己控制线程,我们可以再catch中选择他是继续往下进行还是结束

图解

7线程的其他方法

 详细图解

其要注意的就是前台线程结束后台线程不管如何都会结束,而后台线程结束那么前台线程是不会结束的。

注意事项

3.Thread中的join方法(线程的阻塞)

3.1join方法的本质

(1)

图解

(2)join方法的本质是指的是使用了这个方法的线程就会阻塞

图解

所以main线程永远是最后进行执行的。

其中要注意的就是如果没有设置sleep就是哪个线程先用join那么哪个线程就先执行,还有就是只有start了以后才能join,如果休眠时间一样也按这种方式处理,如果休眠时间不一样,那么休眠间短的线程先执行然后再是休眠时间最长的,最后再是main线程。

3.2代码示例

public class JoinExample {public static void main(String[] args) {Thread thread1 = new Thread(() -> {System.out.println("Thread 1 starting...");try {Thread.sleep(1000); // 模拟耗时操作} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 1 ending...");});Thread thread2 = new Thread(() -> {System.out.println("Thread 2 starting...");try {Thread.sleep(1500); // 模拟耗时操作,比线程1稍微长一些} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Thread 2 ending...");});thread1.start(); // 启动线程1thread2.start(); // 启动线程2try {thread1.join(); // 等待线程1完成thread2.join(); // 等待线程2完成} catch (InterruptedException e) {e.printStackTrace();}System.out.println("Main thread continuing..."); // 当两个子线程都完成后,主线程继续执行}
}

3.3join最大等待时间

1. 概念 join方法可以理解为谁调用这个方法谁就会被阻塞然后等待这个线程运行结束以后然后恢复执行。

2.这个就相当于join等待不能一直等待,我们可以规定一个时间来限制他。

图示(深度解析)

3.4sleep 和join 方法的区别

(1)sleep在java的库中是native方法也就是本地方法,它的底层我们是看不到的

(2)通俗点来讲,sleep是控制这个线程的休眠时间而不是两个代码的执行的间隔时间。

(3)sleep是根据时间戳来对其进行休眠时间的判定

(3)system.currentTimeMillis()这个代码可以获得我们系统的时间戳

注意事项

(4)对于join方法来说,这个就是真正的阻塞,只有前面没有调用这个方法的线程执行结束,这个阻塞才会恢复然后执行这个调用这个join方法的线程 

4.引出线程安全问题(为下一篇文章做基础)

4.1引言

(1)我们之前提到过线程的状态,其中我们也可以叫它为PCB状态,其中linux 和windos系统,linux系统有非常成熟的系统来操控PCB,当然windos系统中也会有选项来对其进行操作,只不过性能方面没有比linux更好罢了。

(2)其中在我们java的jdk中的jvm虚拟机中,nb的大佬程序员都为我们封装好了这些东西来对其进行操作,相较于C++来说,java方便太多,C++语言的特征是性能高稳定但是编写难度较高。

4.2 六种PCB状态梳理

(1)NEW状态

这个状态就是Thread线程的对象已经有了,没有进行start,系统内部的线程还没有创建。

(2).Terminated状态

这个状态是Thread线程的对象有了,也执行完毕,然后被销毁,最后还剩下一个Thread对象。

(3)Runnable状态(就绪状态)

这个指的是,这个线程已经调度到了系统的CPU上面,其中有两种情况。

第一种就是:这个线程已经在CPU上面,但是还没有执行

第二种就是:这个线程已经在CPU上面进行执行了

!!!!!!我看了很多资料讲解的都不是很好,他们将这两种状态分开了,叫出了两种名称,我认为其实没啥用,因为都已经到CPU上面了,已经不是人能决定的了,而是全部由系统决定,你就算再nb你能对系统进行操作吗??所以这两种状态我都称之为就绪状态。

5.阻塞的三种状态讲解

最后一个状态我们在下一篇文章中进行讲解

打开idea的文件夹找到查看线程状态的软件,来对阻塞进行讲解

可以看到,join是死等,join带有最大阻塞时间和sleep休眠是TIMD-WAITING是带有时间的阻塞。

这个的作用就是在你以后工作的时候来判断你的各个线程的状态来更好的开发项目

(1)线程不安全示例

相当于它会重复的对数据进行读取这是非常不好的。

我们可以进行串行执行

这样就好了

所以死锁问题就是这样产生的。

(1)为什么,发生这种问题的简单原因。

 (2)其中在上面的加到5000的情况下面还有一个情况就是两个加在一起小于5w的情况。

在多线程环境中,每个线程都有自己的 CPU 缓存和工作内存。当一个线程修改了变量的值时,这个变化不会立即反映到主内存中,而是在适当的时机才会同步到主内存中。其他线程在读取这个变量的值时,可能会直接从自己的 CPU 缓存或者工作内存中读取,而不是从主内存中读取,这就导致了可能读取到旧值的情况发生。

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

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

相关文章

中国打造成熟工艺产能,台积电力推先进工艺,反杀成功了!

分析机构指出2023年Q4全球芯片市场28纳米及以上工艺芯片占比在快速下降,已跌穿五成,这主要是台积电力推芯片企业向先进工艺发展,与中国大陆打造的成熟工艺芯片抗衡。 分析机构指出2023年Q4全球芯片以工艺划分,28纳米及以上工艺的芯…

考研入门55问---基础知识篇

考研入门55问---基础知识篇 01 >什么是研究生入学考试? 研究生是指大专和本科之后的深造课程。以研究生为最高学历, 研究生毕业后,也可称研究生,含义为研究生学历的人。在中国大陆地区,普通民众一般也将硕士毕业生称…

[入门] Unity Shader前置知识(5) —— 向量的运算

在Unity中,向量无处不在,我想很多人都使用过向量类的内置方法 normalized() 吧,我们都知道该方法是将其向量归一化从而作为一个方向与速度相乘,以达到角色朝任一方向移动时速度都相等的效果,但内部具体是如何将该向量进…

Spring - 8 ( 10000 字 Spring 入门级教程 )

一: MyBatis 1.1 引入 MyBatis 我们学习 MySQL 数据库时,已经学习了 JDBC 来操作数据库, 但是 JDBC 操作太复杂了. 我们先来回顾⼀下 JDBC 的操作流程: 创建数据库连接池 DataSource通过 DataSource 获取数据库连接 Connection编写要执行带 ? 占位符…

21岁的人生赚51W!拒绝捞男捞女,翻身也要爱惜身体!——早读(逆天打工人爬取热门微信文章解读)

身体是革命的本钱 引言Python 代码第一篇 卢克文工作室 捞女在今天的中国是怎样的存在第二篇 人民日报 来啦 新闻早班车要闻社会政策 结尾 我将我的健康视为生活的基石 不会为了短暂的成功而牺牲 我珍惜身体 知道健康是实现梦想的前提 引言 这里毕竟是一个程序员的代码学习平台…

LVS/NAT工作模式介绍及配置

1.1 LVS/NAT模式工作原理 LVS(Linux Virtual Server)的网络地址转换(NAT)模式是一种在网络层(第四层)实现负载均衡的方法。在NAT模式中,Director Server(DS)充当所有服务…

54.HarmonyOS鸿蒙系统 App(ArkTS)tcp socket套接字网络连接收发测试

工程代码https://download.csdn.net/download/txwtech/89258409?spm1001.2014.3001.5501 54.HarmonyOS鸿蒙系统 App(ArkTS)tcp socket套接字网络连接收发测试 import socket from ohos.net.socket; import process from ohos.process; import wifiManager from ohos.wifiMana…

sql 中having和where区别

where 是用于筛选表中满足条件的行,不可以和聚类函数一起使用 having 是用于筛选满足条件的组 ,可与聚合函数一起使用 所以having语句中不能使用select中定义的名字

51单片机软件环境安装

keli5的安装 把CID放到破解程序中 破解程序会给一串数字然后填到那个框中 驱动程序的安装 安装完了以后 设备管理器会出现这个 同时c盘会出现这个文件夹

Flask教程3:jinja2模板引擎

文章目录 模板的导入与使用 模板的导入与使用 Flask通过render_template来实现模板的渲染,要使用这个方法,我们需要导入from flask import rander_template,模板中注释需放在{# #}中 模板的第一个参数为指定的模板文件名称,如自定…

Rust Rocket创建第一个hello world的Web程序 Rust Rocket开发常用网址和Rust常用命令

一、Rust Rocket简介 Rust Rocket 是一个用 Rust 语言编写的 Web 应用框架,它结合了 Rust 的安全性和性能优势,以及 Web 开发的便利性。以下是 Rust Rocket 框架的一些优点: 安全性:Rust 是一种注重安全性的编程语言,…

【C++】学习笔记——vector_2

文章目录 七、vector2. vecotr的使用3. vector的模拟实现 未完待续 七、vector 2. vecotr的使用 上节我们以二维数组结束&#xff0c;这一节我们以二维数组开始。 // 二维数组 vector<vector<int>> vv;二维数组在底层是连续的一维数组。vv[i][j] 是怎样访问的&a…

分布式与一致性协议之一致哈希算法(二)

一致哈希算法 使用哈希算法有什么问题 通过哈希算法&#xff0c;每个key都可以寻址到对应的服务器&#xff0c;比如&#xff0c;查询key是key-01,计算公式为hash(key-01)%3,警告过计算寻址到了编号为1的服务器节点A&#xff0c;如图所示。 但如果服务器数量发生变化&#x…

vue3使用el-autocomplete请求远程数据

服务器端 RestController RequestMapping("/teacher") public class TeacherController {Resourceprivate TeacherService teacherService;GetMapping({"/v1/getTop10TeacherByName/","/v1/getTop10TeacherByName/{name}"})public ResultBean&l…

快速批量重命名文件(夹)

首先&#xff0c;需要用到的这个工具&#xff1a; 度娘网盘 提取码&#xff1a;qwu2 蓝奏云 提取码&#xff1a;2r1z 我这里处理这4个文本&#xff0c;实际可以处理任意数量的文本和文件夹 1、打开工具&#xff0c;进入文件批量复制版块 2、点击“重命名” 3、把要重命名的…

使用Python爬取淘宝商品并做数据分析

使用Python爬取淘宝商品并做数据分析&#xff0c;可以按照以下步骤进行操作&#xff1a; 确定需求&#xff1a;确定要爬取的淘宝商品的种类、数量、关键词等信息。 编写爬虫程序&#xff1a;使用Python编写爬虫程序&#xff0c;通过模拟浏览器请求&#xff0c;获取淘宝商品的页…

Docker 中的 Nginx 服务为什么要启用 HTTPS

一安装容器 1 安装docker-20.10.17 2 安装所需的依赖 sudo yum install -y yum-utils device-mapper-persistent-data lvm23 添加Docker官方仓库 sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo4 安装Docker CE 20.10.17 s…

第182期 23ai:惊喜的全功能缓存True Cache-2安装部署(20240505)

数据库管理182期 2024-05-05 数据库管理-第182期 23ai:惊喜的全功能缓存True Cache-2安装部署&#xff08;20240505&#xff09;1 主机配置2 操作系统配置2.1 基础配置2.2 配置hosts2.3 安装preinstall RPM包2.4 创建目录2.5 配置环境变量 3 部署数据库3.1 部署DB软件3.2 创建监…

机器学习:基于K-近邻(KNN)、高斯贝叶斯(GaussianNB)、SVC、随机森林(RF)、梯度提升树(GBDT)对葡萄酒质量进行预测

前言 系列专栏&#xff1a;机器学习&#xff1a;高级应用与实践【项目实战100】【2024】✨︎ 在本专栏中不仅包含一些适合初学者的最新机器学习项目&#xff0c;每个项目都处理一组不同的问题&#xff0c;包括监督和无监督学习、分类、回归和聚类&#xff0c;而且涉及创建深度学…

linux上如何排查JVM内存过高?

怎么排查JVM内存过高&#xff1f; 前言&#xff1a; 想必工作一两年以后的同学都会逐渐面临到&#xff0c;jvm等问题&#xff0c;但是可能苦于无法熟练的使用一些工具&#xff1b;本文将介绍几个比较常用分析工具的使用方法&#xff0c;带着大家一步步定位分析问题。 1、top 查…