JavaEE 多线程详细讲解(1)

news2024/12/30 3:43:51

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() {
            @Override
            public 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(); // 启动线程1
        thread2.start(); // 启动线程2

        try {
            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.coloradmin.cn/o/1643375.html

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

相关文章

Python-VBA函数之旅-open函数

目录 一、open函数的常见应用场景 二、open函数使用注意事项 三、如何用好open函数? 1、open函数: 1-1、Python: 1-2、VBA: 2、推荐阅读: 个人主页:神奇夜光杯-CSDN博客 一、open函数的常见应用场…

【平时工作中的各种术语__持续更新~~~~】

中文: 1、jar包 JAR包(Java Archive Package)是一种将多个Java类文件以及与它们相关的元数据和资源(如文本、图片等)打包到一个单一文件中的归档工具。它基于ZIP文件格式。JAR文件主要用于分发和部署Java应用程序。J…

GitHub Desktop安装与使用教程

GitHub Desktop 是GitHub公司推出的一款桌面应用程序,旨在帮助开发人员更轻松使用GitHub。它提供了一个直观的用户界面,允许用户通过图形化界面来执行常见的 Git 操作,如克隆仓库、创建分支、提交更改、合并代码等。 GitHub Desktop 的设计使…

数仓开发:DIM层数据处理

一、了解DIM层 这个就是数仓开发的分层架构 我们现在是在DIM层,从ods表中数据进行加工处理,导入到dwd层,但是记住我们依然是在DIM层,而非是上面的ODS和DWD层。 二、处理维度表数据 ①先确认hive的配置 -- 开启动态分区方案 -- …

AI智体的分级:从基于规则到基于LLM

摘要: AI智体被定义为感知环境、做出决策和采取行动的人工实体。受SAE(汽车工程师学会)自动驾驶6个级别的启发,AI智体也根据效用和强度进行分类,分为以下几个级别:L0——无AI,有工具&#xff0…

proc 文件系统

proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口 , 用户和应用程序可以通过 proc 文件系统得到系统信息和进程相关信息 ,对 proc 文件系统的读写作为与内核进行通信的一种手段。但是与普通文件不同的…

Flutter笔记:Widgets Easier组件库(12)使用消息吐丝(Notify Toasts)

Flutter笔记 Widgets Easier组件库(12)使用消息吐丝(Notify Toasts) - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite:http://thispage.tech/Email: 29114848416…

【软考高项】三十二、成本管理域计算题相关概念和公式

一、相关缩写表示 BACBudget at Completion----完工预算 ACActual Cost----实际成本 EVEarned Value----挣值 PVPlanned Value----计划价值 CVCost Variance----成本偏差 SVSchedule Variance----进度偏差 CPICost Performance Index----成本绩效指数 SPI Schedule Performanc…

RSA加密---java和node兼容版(可直接复制使用)

目录 背景 实现 一、node代码 1、引入依赖 2、生成公钥和私钥 3、生成工具类 二、java代码 背景 本来项目的后端是node,里面登录接口用的是后端生成RSA公钥和私钥,公钥给前端网页用来加密,node后端解密,一切很和谐&#x…

带环链表和链表的复制,检验你链表的学习情况

前言:带环链表是链表中的经典问题,需要一定的数理思维,一定要掌握其来龙去脉,这样可以加深理解。本文主要讲解一下个人对带环链表的理解。 带环链关的OJ题 1.判断链表是否带环 题目: 141. 环形链表 给你一个链表的头…

深入理解网络原理2----UDP协议

文章目录 前言一、UDP协议协议段格式(简图)校验和 二、UDP与TCP 前言 随着时代的发展,越来越需要计算机之间互相通信,共享软件和数据,即以多个计算机协同⼯作来完成业务,就有了⽹络互连。 一、UDP协议 协…

GRU模块:nn.GRU层的输出state与output

在 GRU(Gated Recurrent Unit)中,output 和 state 都是由 GRU 层的循环计算产生的,它们之间有直接的关系。state 实际上是 output 中最后一个时间步的隐藏状态。 GRU 的基本公式 GRU 的核心计算包括更新门(update gat…

【杨辉三角,递归】

参考代码&#xff1a; #include<bits/stdc.h> using namespace std; int pos(int x,int y) {if(y1||xy)return 1;elsereturn pos(x-1,y-1)pos(x-1,y); }int main() {int n0; //若输入的行数太大&#xff0c;三角形的样式会不规则&#xff0c;因此这里用while来控制输入的…

单片机的中断

中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置 当中央处理机CPU正在处理某件事的时候外界发生紧急事件请求&#xff0c;要CPU暂停当前的工作&#xff0c;转而去处理这个紧急事件&#xff0c;处理完以后&#xff0c;再回到原来中断的地方&#xff0c;继续原来的工作…

环形链表的判断方法与原理证明

&#xff08;题目来源&#xff1a;力扣&#xff09; 一.判读一个链表是否是环形链表 题目&#xff1a; 解答&#xff1a; 方法&#xff1a;快慢指针法 内容&#xff1a;分别定义快慢指针&#xff08;fast和slow&#xff09;&#xff0c;快指针一次走两步&#xff0c;慢指…

2024 年 7 款最好的 iOS 系统修复软件

您的 iOS 设备是否时不时地出现问题&#xff1f;因此屏幕卡住&#xff0c;或者无法退出恢复模式&#xff1f; 即使是最新的 iPhone 型号&#xff0c;也可能存在大量错误。解决所有这些错误的最佳且安全的方法是使用iOS系统修复工具&#xff0c;让您的修复工作变得更加轻松。 …

KIE基于图模型的关键信息抽取源码详解

1.数据集准备 下载数据集 https://download.openmmlab.com/mmocr/data/wildreceipt.tar WildReceiptOpenset 准备好 WildReceipt。 转换 WildReceipt 成 OpenSet 格式: # 你可以运行以下命令以获取更多可用参数: # python tools/dataset_converters/kie/closeset_to_opens…

贪心算法(活动选择、分数背包问题)

一、贪心算法 贪心算法是指&#xff1a;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择&#xff0c;而不从整体最优考虑&#xff0c;做出的仅是在某种意义上的局部最优解。 …

[C++基础学习-07]----C++结构体详解

前言 结构体&#xff08;Struct&#xff09;是C中一种用户定义的复合数据类型&#xff0c;用于存储不同类型的数据项。结构体可以包含不同类型的数据成员&#xff0c;这些数据成员可以是基本类型&#xff08;如int、float、char等&#xff09;&#xff0c;也可以是数组、指针、…

【莫比乌斯变换-04】求解莫比乌斯变换系数

求解莫比乌斯变换系数 文章目录 一、说明二、如何确定双线性变换系数2.1 变换基本形式2.2 通过三点确定 三、一般情况的变换3.1 最简单的情况&#xff1a;无穷大3.2 处理无穷大 四、Python 代码 一、说明 上一篇文章是对双线性变换的视觉介绍&#xff0c;又名莫比乌斯变换或分…