当前位置: 首页 > news >正文

初始JavaEE篇——多线程(1):Thread类的介绍与使用

 找往期文章包括但不限于本期文章中不懂的知识点:

个人主页:我要学编程(ಥ_ಥ)-CSDN博客

所属专栏:JavaEE

目录

创建线程

1、继承 Thread类

2、实现Runnable接口

3、使用匿名内部类

1)继承Thread类的匿名内部类

2)实现Runnable接口

查看线程的状态

Thread类的常见属性与方法


创建线程

Thread 是线程的意思,在Java中也是通过 Thread类中的 start 方法启动一个线程。但要注意的是,每一个线程对象都只能调用一次 start方法,如果后续再去调用的话,便会抛异常。

在Java中有四种方式实现多线程。

1、继承 Thread类

语法格式:

// 继承Thread类
class MyThread extends Thread {@Overridepublic void run() {....... // 代码}
}public class Test {public static void main(String[] args) {Thread t = new MyThread();// 启动 t线程,调用run方法t.start();// 后续代码.......}
}

 我们创建一个新的类,使其继承Thread类,并重写其中的run方法。这就已经具备了一个线程的基本雏形,现在只要我们在main线程中去实例化这个类,并且启动这个线程即可。启动线程的方法是通过调用线程对象的start方法,start方法会启动一个新的线程并在这个新线程中自动调用run方法,完成该线程的所执行的任务。

// 1、继承Thread类
class MyThread extends Thread {@Overridepublic void run() { // 重写run方法while (true) {System.out.println("Hello Thread");}}
}public class Test {public static void main(String[] args) {Thread t = new MyThread();// 启动 t线程,调用run方法t.start(); // 这里发生了多态while (true) {System.out.println("Hello main");}}
}

上述代码运行后,会出现 Hello main 和 Hello Thread  循环交替的打印,这便是多线程。main线程在运行的同时,t 线程也在运行。

从结果上也能看出这是在并发执行,main线程在CPU上运行一段时间,t 线程接着在CPU上运行一段时间。 我们可以使用 sleep 方法来放慢观察。与C语言中的sleep函数一样,是让程序休眠的方法。下面就来详细介绍一下:

Java中的sleep方法是Thread类的一个静态方法,参数是要休眠时间,单位是毫秒。我们可以先写一个代码来演示:

public class Test {public static void main(String[] args) throws InterruptedException {while (true) {System.out.println("Hello main");Thread.sleep(1000); // 会抛异常,需要throws或者try-catch}}
}

上面的代码会打印一个 Hello main 之后,休眠1000毫秒,即1秒。 

 注意:这个休眠是让线程主动的放弃CPU资源。

现在我们可以近距离观察多线程的运行情况了。

// 1、继承Thread类
class MyThread extends Thread {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();// 启动 t线程,调用run方法t.start(); // 这里发生了多态while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

运行结果: 

有细心的小伙伴应该发现了,使用sleep方法时, run方法和main方法中的处理方式不一样,run方法是通过try-catch的形式,而main方法中两种都是可以的。这是因为run方法是重写了Thread类中的run方法,父类的没有throws这个操作,因此子类在重写时也不能有throws操作,不然就会改变run方法的框架,从而不符合重写的概念。 

2、实现Runnable接口

// 实现Runnable接口
class MyRunnable implements Runnable {@Overridepublic void run() {....... // 代码}
}public class Test {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());// 启动 t线程,并调用run方法t.start();// 后续代码}
}

我们创建一个新的类,实现 Runnable接口,重写 run方法。接着在main方法中将实例化一个Thread类对象,将 实现Runnable接口的类实例化作为参数传入Thread中。

代码演示:

class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(new MyRunnable());// 启动 t线程,调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

运行结果:

注意:我们使用t.run方法也能执行代码,但是得不到 t.start的效果。因为t.run方法不会创建线程,只是一个简单的方法调用,那么最终代码就会一直死循环打印 Hello Thread。如下所示:

使用 t.start 时,是先创建的线程,然后再去调用的run方法,因此 t 线程和main线程会并发执行。 

上面是两种主要的创建线程的方式,剩下的两种就是通过对上面的变形来编写的。

3、使用匿名内部类

使用匿名内部类有两种选择,一个是继承Thread类,一个是实现Runnable接口

1)继承Thread类的匿名内部类

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(){@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}};// 启动 t线程,调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

 当然除了上面这种写法,还有另一种写法:

public class Test {public static void main(String[] args) throws InterruptedException {new Thread(){@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}}.start(); // 这里启动了线程,并调用了run方法while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

第一种写法是通过父类引用来接收子类对象,而第二种写法就是一次性的线程,当这次使用完成之后,后续再也不能使用该线程了。

2)实现Runnable接口

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(// 创建一个匿名对象,该对象是实现了Runnable接口的匿名内部类new Runnable() {@Overridepublic void run() {while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}});// 启动 t线程,并调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

 由于 Runnable 接口是函数式接口(一个接口中只包含一个抽象方法),因此我们可以使用lambda表达式来简写。

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 启动 t线程,并调用run方法t.start();while (true) {System.out.println("Hello main");Thread.sleep(1000);}}
}

lambda表达式:"()" 这个要重写的方法的参数 ,"->" 是必不可少的,"{}" 这里面是要重写的方法的方法体,即具体的代码。

以上就是创建线程的全部方法了。

我们在日常的开发中,使用最多的就是通过实现 Runnable 接口来创建线程。因为我们在使用 Runnable 接口创建线程时,是将实现该接口的对象作为参数传入,这样后续如果要修改代码的话,也只需要在实现该接口的内部进行修改,而不需要再去改动main线程中的代码了。这样代码之间的依赖程度就降低了,而是通过模块来之间来依赖维护,也就达到了高内聚、低耦合的效果。而使用 lambda 表达式来简写代码的方式则是既方便,可读性也不差。因此我们基本上是使用 lambda 表达式来简写实现 Runnable 接口来创建线程。

查看线程的状态

我们可以通过JDK自带的工具来查看当前进程的状态以及具体线程的相关状态。

1、找到安装JDK的目录。

2、找到bin目录下的 jconsole 这个.exe文件,并双击运行。

3、找到要连接的进程,一般对应的类名,然后点击连接即可。下图是双击运行后的界面:

以上就是关于如果查看线程相相关信息的操作。

Thread类的常见属性与方法

上面我们是采用了Thread类的两种构造方法来创建线程的,其实除了上面的方法外还有其他的几种构造方法。

方法描述
Thread()创建线程对象
Thread(Runnable target)使用Runnable对象创建线程对象
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group, Runnable target)这个暂时不需要用到,后期再学习

注意:使用 Thread() 和 Thrad(String name),这两个方法都是要重写 run 方法的,而另外两个虽然使用 Runnable,但本质上还是要重写 run 方法。因此对于一个线程最重要的就是 run方法,虽然 start 方法是启动线程,但是只要 run 方法执行完毕,通常这个线程就会被销毁。  

下面就来学习线程的常见属性:

属性获取方法
IDgetld()
名称getName()
状态getState()
优先级getPriority()
是否后台线程(守护线程)isDaemon()
是否存活isAlive()
是否被中断islnterrupted()

解释:

1、ID 是线程的唯一标识、名称是各种调试工具用到、状态表示线程当前所处的一个情况:就绪态、阻塞态、运行态等。

2、优先级是指当多个线程同时被调度时,由于是随机调度的,不能确保某个线程一定会被第一时间执行,但如果某个线程的优先级比较高的话,那么就会先运行该线程。

3、后台线程也叫作守护线程,即这个线程只是默默守护, 并不会干扰到程序的正常运行,当程序正常运行结束后(进程结束了),后台线程也就被销毁了,但是前台线程不一样,只有当全部的前台线程运行结束后,进程才会结束。简单理解就是,后台线程不能干预进程的结束,而前台线程可以干预进程的结束。

从上面,我们查看线程的状态那里可以看出来,我们自己创建的线程默认都是前台线程。我们也可以通过代码来观察:

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (true) {System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});// 设置成后台线程t.setDaemon(true);// 启动 t线程,并调用run方法t.start();for (int i = 0; i < 3; i++) {System.out.println("Hello main");Thread.sleep(1000);}System.out.println("main线程结束!进程也就结束了");}
}

运行结果:

注意:设置线程的属性一定要在调用start方法之前,因为一旦调用start方法之后,线程启动了,那么就是按照其默认的属性来进行运行了,则最终不会停下来。

4、线程是否存活,简单理解就是看run方法是否执行完毕,但有些特殊情况下,即使run方法执行完毕,线程依旧存活。

我们也是可以通过代码来观察的。

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{System.out.println("Hello Thread");System.out.println("t线程结束");});// 看看线程启动前是否存活System.out.println(t.isAlive());t.setDaemon(true);// 启动 t线程,并调用run方法t.start();// 看看结束前是否存活System.out.println(t.isAlive());// 确保 t线程已经结束了Thread.sleep(1000);// 看看结束后是否存活System.out.println(t.isAlive());System.out.println("main线程结束,进程结束!");}
}

运行结果:

从运行的结果,我们可以看出启动前、结束后线程都已经灭亡了,当线程还在执行run方法时,线程就是存活的。 

5、线程的中断就是让线程结束,而让线程结束就只有一个办法:让 run方法提前结束,而让run方法提前就是让while循环提前结束,即在while循环的条件语句中来判断。

我们可以用代码来演示:

public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{while (!Thread.currentThread().isInterrupted()) { // 判断是否是中断System.out.println("Hello Thread");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("t线程结束");});// 启动 t线程,并调用run方法t.start();Thread.sleep(3000);// 中断t.interrupt();System.out.println("main线程结束");}
}

运行结果:

首先,来解读代码:

1、因为 t 在lambda表达式之后才才会创建,因此我们在调用 isInterrupted 方法时,不能通过 t 来调用,要通过 Thread.currentThread 方法来获取当前的类对应的对象,然后再用这个对象去调用 isInterrupted 方法。

2、Thread.currentThread().isInterrupted() 方法会返回 false ,因为线程在执行的过程中是处于非中断的状态,因此我们要对其进行 取反操作,才能使其进入while循环。

3、 当我们编写 t.interrupt() 的代码时,线程确实会中断,但是由于 打印语句的执行是非常快的,因此在 t 线程的生命周期中,绝大部分时间是处于休眠状态,而此方法会唤醒sleep,使其抛出异常,因此只要我们在 throw 异常的代码中,使用break 或者 啥也不做,那么就不会抛出异常。

上述是使用 break的效果,但如果我们 啥也不干的话,发现不会停下来。

其实是 sleep 在捣乱。正常来说,调用 Interrupt 方法将 isInterrupted 方法内部的标志位设置为了 true ,但由于上述代码中把 sleep 给唤醒了,这种提前唤醒的情况下,sleep 就会在唤醒之后,把 islnterrupted 标志位给设置回 false。所以后续在进行判断的时候,还是会进入while循环。

好啦!本期 初始JavaEE篇——多线程(1):Thread类的介绍与使用 的学习之旅到此结束啦!我们下一期再一起学习吧!

相关文章:

初始JavaEE篇——多线程(1):Thread类的介绍与使用

找往期文章包括但不限于本期文章中不懂的知识点&#xff1a; 个人主页&#xff1a;我要学编程(ಥ_ಥ)-CSDN博客 所属专栏&#xff1a;JavaEE 目录 创建线程 1、继承 Thread类 2、实现Runnable接口 3、使用匿名内部类 1&#xff09;继承Thread类的匿名内部类 2&#xff09…...

基于单片机的LED照明自动控制系统的设计

本设计主控核心芯片选用了AT89C51单片机&#xff0c;接入了光照采集模块、红外感应模块、继电器控制模块&#xff0c;通过控制发光二极管模拟教室智能灯组的控制。首先通过光敏感应的方式感应当前光照环境为白天还是夜晚&#xff0c;同时&#xff0c;红外感应模块感应是否有人。…...

C语言——头文件的使用

目录 前言头文件怎么包含 前言 这个专栏会专门讲一些C语言的知识&#xff0c;后续会慢慢更新&#xff0c;欢迎关注 C语言专栏 头文件怎么包含 在使用头文件的过程中&#xff0c;我们经常会遇到重定义、重复包含等问题&#xff0c;那么怎么编写头文件和使用头文件才能解决这些…...

LeetCode 精选 75 回顾

目录 一、数组 / 字符串 1.交替合并字符串 &#xff08;简单&#xff09; 2.字符串的最大公因子 &#xff08;简单&#xff09; 3.拥有最多糖果的孩子&#xff08;简单&#xff09; 4.种花问题&#xff08;简单&#xff09; 5.反转字符串中的元音字母&#xff08;简单&a…...

【Unity - 屏幕截图】技术要点

在Unity中想要实现全屏截图或者截取某个对象区域的图片都是可以通过下面的函数进行截取 Texture2D/// <summary>/// <para>Reads the pixels from the current render target (the screen, or a RenderTexture), and writes them to the texture.</para>/…...

句句深刻,字字经典,创客匠人老蒋金句出炉,哪一句让你醍醐灌顶?

注意力经济时代、流量经济时代、短视频经济时代&#xff0c;创始人到底应该如何做&#xff0c;才能抓住风口&#xff0c;链接未来&#xff1f; 「创始人IP创新增长班」线下大课现场&#xff0c;老蒋作为主讲导师&#xff0c;再一次用他丰富的行业经验与深刻的时代洞察&#xff…...

柯尼卡美能达CA-310 FPD色彩分析仪

柯尼卡美能达CA-310 FPD色彩分析仪 型  号&#xff1a;CA-310 名  称&#xff1a;FPD色彩分析仪 品  牌&#xff1a;柯尼卡美能达(KONICA MINOLTA) 分  类&#xff1a;光学和色彩测试 > 光学、显示与色彩测量 > 色彩分析仪 产品属性&#xff1a;主机 简  述&…...

二维EKF的MATLAB代码

EKF二维滤波 MATLAB 实现 提升您的数据处理能力&#xff01;本MATLAB程序实现了扩展卡尔曼滤波&#xff08;EKF&#xff09;在二维状态估计中的应用&#xff0c;专为需要高精度定位和动态系统分析的用户设计。通过精确的滤波技术&#xff0c;有效减少噪声影响&#xff0c;确保…...

大数据治理:数据时代的挑战与应对

目录 大数据治理&#xff1a;数据时代的挑战与应对 一、大数据治理的概念与内涵 二、大数据治理的重要性 1. 提高数据质量与可用性 2. 确保数据安全与合规 3. 支持数据驱动的决策 4. 提高业务效率与竞争力 三、大数据治理的实施策略 1. 建立健全的数据治理框架 2. 数…...

绿联NAS免驱安装MacOS

前段时间UGOS Pro迎来了一次大更新&#xff0c;Docker新增了Docker Compose堆栈项目&#xff0c;于是便在Docker Hub找了个支持Docker Compose部署的MacOS开源项目来验证一下&#xff0c;顺便体验一下用N100运行是什么感觉。 开始折腾 先说说&#xff0c;在没用Docker Compos…...

聊聊ASSERT处理在某些场景下的合理用法

先看看ASSERT的介绍&#xff1a; 编写代码时&#xff0c;我们总是会做出一些假设&#xff0c;ASSERT断言就是用于在代码中捕捉这些假设&#xff0c;可以将断言看作是异常处理的一种高级形式。断言表示为一些布尔表达式&#xff0c;程序员相信在程序中的某个特定点该表达式值为真…...

SAP Odata 服务

参考过程 SAP创建ODATA服务-Structure_sap odata-CSDN博客 案例...

【java数据结构】栈

【java数据结构】栈 一、栈的概念二、 栈的使用三、 栈的模拟实现(数组)构造方法size()empty()push()pop()peek() 四、 栈的模拟实现(链表)构造方法size()empty()push()pop()peek() 五、 栈的例题 此篇博客希望对你有所帮助&#xff08;帮助你了解栈&#xff09;&#xff0c;不…...

从头开始的可视化数据 matplotlib:初学者努力绘制数据图

从头开始学习使用 matplotlib 可视化数据&#xff0c;对于初学者来说&#xff0c;可能会有些挑战&#xff0c;但 matplotlib 的核心理念非常清晰&#xff1a;绘制图表需要了解如何设置图形、坐标轴以及如何用数据填充它们。我们可以通过一些简单的例子来逐步介绍基本步骤。 1. …...

vscode 远程linux服务器 连接git

vscode 远程linux服务器 连接git 1. git 下载2. git 配置1&#xff09;github 设置2&#xff09;与github建立连接linux端&#xff1a;创建密钥github端&#xff1a;创建ssh key 3. 使用1&#xff09;初始化repository2&#xff09;commit 输入本次提交信息&#xff0c;提交到本…...

不同jdk版本中的接口规范

Java Development Kit&#xff08;JDK&#xff09;的每个版本通常会对 Java 语言和类库进行改进&#xff0c;接口规范也在不断演进。Java 接口的演变是逐步从 “纯粹抽象的定义” 向 “具有行为的抽象定义” 演化的。 JDK 1.0 和 JDK 1.1JDK 1.2 到 JDK 1.6JDK 1.8&#xff08;…...

人工智能图像信号处理器(AI ISP)技术介绍

随着智能设备和数码成像技术的快速发展&#xff0c;图像质量的提升成为用户体验的关键因素之一。人工智能图像信号处理器&#xff08;AI Image Signal Processor&#xff0c;AI ISP&#xff09; 作为传统图像信号处理器&#xff08;ISP&#xff09;的升级版&#xff0c;通过集成…...

3D Slicer 教程三 ---- 坐标系

上篇提到3D Slicer 教程二 ---- 数据集-CSDN博客 3d slicer的坐标系与大多数医学影像软件使用LPS&#xff08;左、后、上&#xff09;坐标系统不太一样, 今天就仔细介绍一下坐标系的区别,复盘一下在影像处理中遇到的坐标问题(集中在坐标处理相关的,图像插值,图像处理, 定位线,翻…...

Video-LLaMA论文解读和项目部署教程

Video-LLaMA: An Instruction-tuned Audio-Visual Language Model for Video Understanding 相关工作 大型语言模型&#xff1a; 本文的工作基于这些LLM&#xff0c;并提供即插即用插件&#xff0c;使其能够理解视频中的视觉和听觉内容。 多模态大型语言模型&#xff1a; 现有…...

Elasticsearch设置 X-Pack认证,设置账号和密码

前言 以下Elasticsearch版本&#xff1a;7.9.3 ES自带的X-Pack密码验证&#xff1a; X-Pack是elasticsearch的一个扩展包&#xff0c;将安全&#xff0c;警告&#xff0c;监视&#xff0c;图形和报告功能捆绑在一个易于安装的软件包中&#xff0c;所以我们想要开启账号密码验证…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

mongodb源码分析session执行handleRequest命令find过程

mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程&#xff0c;并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令&#xff0c;把数据流转换成Message&#xff0c;状态转变流程是&#xff1a;State::Created 》 St…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

浅谈不同二分算法的查找情况

二分算法原理比较简单&#xff0c;但是实际的算法模板却有很多&#xff0c;这一切都源于二分查找问题中的复杂情况和二分算法的边界处理&#xff0c;以下是博主对一些二分算法查找的情况分析。 需要说明的是&#xff0c;以下二分算法都是基于有序序列为升序有序的情况&#xf…...

USB Over IP专用硬件的5个特点

USB over IP技术通过将USB协议数据封装在标准TCP/IP网络数据包中&#xff0c;从根本上改变了USB连接。这允许客户端通过局域网或广域网远程访问和控制物理连接到服务器的USB设备&#xff08;如专用硬件设备&#xff09;&#xff0c;从而消除了直接物理连接的需要。USB over IP的…...

JavaScript基础-API 和 Web API

在学习JavaScript的过程中&#xff0c;理解API&#xff08;应用程序接口&#xff09;和Web API的概念及其应用是非常重要的。这些工具极大地扩展了JavaScript的功能&#xff0c;使得开发者能够创建出功能丰富、交互性强的Web应用程序。本文将深入探讨JavaScript中的API与Web AP…...

MySQL 8.0 事务全面讲解

以下是一个结合两次回答的 MySQL 8.0 事务全面讲解&#xff0c;涵盖了事务的核心概念、操作示例、失败回滚、隔离级别、事务性 DDL 和 XA 事务等内容&#xff0c;并修正了查看隔离级别的命令。 MySQL 8.0 事务全面讲解 一、事务的核心概念&#xff08;ACID&#xff09; 事务是…...

windows系统MySQL安装文档

概览&#xff1a;本文讨论了MySQL的安装、使用过程中涉及的解压、配置、初始化、注册服务、启动、修改密码、登录、退出以及卸载等相关内容&#xff0c;为学习者提供全面的操作指导。关键要点包括&#xff1a; 解压 &#xff1a;下载完成后解压压缩包&#xff0c;得到MySQL 8.…...