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() {
            @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 缓存或者工作内存中读取,而不是从主内存中读取,这就导致了可能读取到旧值的情况发生。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Java】面向对象核心知识点(三),文章层次分明,内容精益求精,代码简单易懂

目录 一、对象类型转换 1.1 情况 1.2 语法 1.3 代码 1.4 结论 二、static关键字 2.1 作用 2.2 代码 三、final关键字 3.1 作用 3.2 代码 四、instanceof关键字 4.1 作用 4.2 代码 (原创文章,转载请注明出处) 博主是计算机专业…

StableDiffusion 文生视频教程,从Mov2mov到AnimateDiff

文章目录 0. 前言1. 简介2. 文生视频2.1 Mov2mov2.1.1 插件安装2.1.2 视频生成 2.2 ffmpeg Ebsynth2.2.1 ffmpeg 安装2.2.2 Ebsynth安装2.2.3 Ebsynth 插件安装2.2.4 视频生成2.2.4.1 Step 1 蒙版裁剪2.2.4.2 Step2 识别关键帧2.2.4.3 Step3~4 关键帧重绘2.2.4.3 Step5~6 生成…

n-Track Studio Suite for Mac激活版:打造您的专属音频工作室

n-Track Studio Suite for Mac是一款功能强大的数字音频工作站软件,让您在家中就能享受到专业录音棚的待遇。无论是录制人声、乐器还是MIDI序列,都能轻松应对。 n-Track Studio Suite for Mac激活版下载 这款软件拥有实时音高校准、时间拉伸和自动补足功…

18 内核开发-内核重点数据结构学习

课程简介: Linux内核开发入门是一门旨在帮助学习者从最基本的知识开始学习Linux内核开发的入门课程。该课程旨在为对Linux内核开发感兴趣的初学者提供一个扎实的基础,让他们能够理解和参与到Linux内核的开发过程中。 课程特点: 1. 入门级别&…

9.4.k8s的控制器资源(job控制器,cronjob控制器)

目录 一、job控制器 二、cronjob控制器 一、job控制器 job控制器就是一次性任务的pod控制器,pod完成作业后不会重启,其重启策略是:Never; 简单案例 启动一个pod,执行完成一个事件,然后pod关闭;…

破解keil uvision4 4.12时遇到:*** TOOLS.INI: TOOLCHAIN NOT INSTALLED **

1 使用管理员权限打开keil和注册机。 2 将注册机中的target改为ARM。

免费开源,无需 GPU,本地化部署大语言模型的对话系统

免费开源,无需 GPU,本地化部署大语言模型的对话系统 分类 编程技术 项目名: FreeAskInternet -- 本地化部署大语言模型的对话系统 Github 开源地址: https://github.com/nashsu/FreeAskInternet FreeAskInternet 是一个免费开源的工具&am…

ZOC8 for Mac v8.08.1激活版:卓越性能的SSH客户端

在远程连接和管理的世界中,ZOC8 for Mac以其卓越的性能和丰富的功能,成为了众多专业人士的首选SSH客户端。它支持SSH1、SSH2、Telnet、Rlogin、Serial等多种协议,让您轻松连接到远程服务器。ZOC8拥有简洁直观的界面和强大的功能设置&#xff…

STM32 F103C8T6学习笔记16:1.3寸OLED的驱动显示日历

今天尝试使用STM32 F103C8T6驱动显示 1.3寸的OLED,显示数字、字符串、汉字、图片等 本质与0.96寸的OLED是完全相同的原理: 而且经过我的研究发现: 1.3寸大小的OLED并未比0.96寸的有更多的显示像素点数来显示,也是128*64的像素点数显示: 也…

【设计模式】函数式编程范式工厂模式(Factory Method Pattern)

目录标题 定义函数式接口函数式接口实现类工厂类封装实际应用总结 定义函数式接口 ISellIPad.java /*** 定义一个函数式接口* param <T>*/ FunctionalInterface public interface ISellIPad<T> {T getSellIPadInfo();}函数式接口实现类 HuaWeiSellIPad.java pu…

头歌实践教学平台:三维图形观察OpenGL1.0

第1关&#xff1a;模型变换 一.任务描述 根据提示&#xff0c;在右侧修改代码&#xff0c;并自己绘制出图形。平台会对你编写的代码进行测试。 1.本关任务 学习了解三维图形几何变换原理。 理解掌握OpenGL三维图形几何变换的方法。 理解掌握OpenGL程序的模型视图变换。 掌握…

【机器视觉】C# .NET 8 部署yolov9 onnx对象检测

这段代码展示了一个使用YOLOv9进行对象检测的简单测试框架。代码主体以及其功能分为以下几个关键部分&#xff1a; 创建测试图片数组 _testImages&#xff0c;它包含了图片文件名和对应的标签。使用 buildTests 方法来从给定的文件名中加载图片并调整尺寸&#xff0c;准备测试数…

【每日力扣】141. 环形链表与142. 环形链表 II

&#x1f525; 个人主页: 黑洞晓威 &#x1f600;你不必等到非常厉害&#xff0c;才敢开始&#xff0c;你需要开始&#xff0c;才会变的非常厉害 141. 环形链表 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟…

利用策略模式+模板方法实现项目中运维功能

前段时间项目中有个需求&#xff1a;实现某业务的运维功能&#xff0c;主要是对10张数据库表的增删改查&#xff0c;没有复杂的业务逻辑&#xff0c;只是满足运维人员的基本需要&#xff0c;方便他们快速分析定位问题。这里简单记录分享下实现方案&#xff0c;仅供参考。 一、…

学习Rust的第27天:Rust中的pwd

过去几天我们一直在重新创建 GNU 核心实用程序的基本功能&#xff0c;而今天&#xff0c;我们将做一些有点太简单的事情&#xff0c; pwd 这个实用程序是用于打印Linux终端中的工作目录。 Understanding the utility 了解实用程序 Running the pwd command, we get an output l…

SpringBoot使用AOP注解记录操作日志

一、前言 日志&#xff1a;指系统所指定对象的某些操作和其操作结果按时间有序的集合。 操作日志&#xff1a;主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改&#xff0c;操作日志要求可读性比较强。比如张三在某个时间下了订单买了某个商品&#xff01; …

linux实验(数据库备份)

以下所有操作皆以机房电脑上的虚拟机为基础环境 下载链接&#xff1a;Linux课程机房虚拟机# 切换到root用户 su - root安装数据库mysql 5.7 rpm -ivh https://mirrors4.tuna.tsinghua.edu.cn/mysql/yum/mysql-5.7-community-el7-x86_64/mysql-community-common-5.7.29-1.el7.x…

Llama改进之——SwiGLU激活函数

引言 今天介绍LLAMA模型引入的关于激活函数的改进——SwiGLU1&#xff0c;该激活函数取得了不错的效果&#xff0c;得到了广泛地应用。 SwiGLU是GLU的一种变体&#xff0c;其中包含了GLU和Swish激活函数。 GLU GLU(Gated Linear Units,门控线性单元)2引入了两个不同的线性层…

Linux(openEuler、CentOS8)常用的IP修改方式(文本配置工具nmtui+配置文件+nmcli命令)

----本实验环境为openEuler系统<以server方式安装>&#xff08;CentOS类似&#xff0c;可参考本文&#xff09;---- 一、知识点 &#xff08;一&#xff09;文本配置工具nmtui(openEuler已预装) nmtui&#xff08;NetworkManager Text User Interface&#xff09;是一…

ZooKeeper以及DolphinScheduler的用法

目录 一、ZooKeeper的介绍 数据模型 ​编辑 操作使用 ①登录客户端 ​编辑 ②可以查看下面节点有哪些 ③创建新的节点&#xff0c;并指定数据 ④查看节点内的数据 ⑤、删除节点及数据 特殊点&#xff1a; 运行机制&#xff1a; 二、DolphinScheduler的介绍 架构&#…