【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)
➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan
欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。
本文章CSDN首发,欢迎转载,要注明出处哦!
先感谢优秀的你能认真的看完本文,有问题欢迎评论区交流,都会认真回复!
上一篇博文:【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(一)
深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)
- 一、✅线程的实现方式
- 1.1✅使用内核线程实现
- 1.2✅使用用户线程实现
- 1.3✅使用用户线程加轻量级进程混合实现
- 一、✅拓展知识仓
- 2.1✅内核线程有什么优点和缺点
- 2.2✅内核线程和用户线程的区别
- 2.3✅内核线程有哪些应用场景
- 2.4✅Java的线程实现
- 2.5✅虚拟线程
- 2.6 ✅虚拟线程和平台线程的区别
- 2.7✅如何使用
- 2.8 ✅性能差异
一、✅线程的实现方式
我们都知道,在操作系统中,线程是比进程更轻量级的调度执行单位,线程的引入可以把一个进程的盗源分配和执行调度分开,各个线程既可以共享进程资源,又可以独立调度。
其实,线程的实现方式主要有三种: 分别是使用内核线程实现、使用用户线程实现以及使用用户线程加轻量级进程混合实现。
1.1✅使用内核线程实现
内核线程(Kernel-Level Thread,KLT) 就是直接由操作系统内核 (Kernel) 支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler) 对线程进行调度,并负责将线程的任务映射到各个处理器上,并向应用程序提供API接口来管理线程。
应用程序一般不会直接去使用内核线程,而是去使用内核线程的一种高级接口—— 轻量级进程 (LightWeight Process,LWP)
,轻量级进程就是我们通常意义上所进的线程,由于每个轻量级进程都由一个内核线程支持,因此只有先支持内核线程,才能有轻量级进程。
有了内核线程的支持,每个轻量级进程都成为一个独立的调度单元,即使有一个轻量级进程在系统调用中阻塞了,也不会影响整个进程继续工作。
但是轻量级进程具有它的局限性:首先,由于是基于内核线程实现的,所以各种线程操作,如创建、析构及同步,都需要进行系统调用。而系统调用的代价相对较高,需要在用户态 (User Mode)和内核态(Kernel Mode)中来回切换。 其次,每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源(如内核线程的栈空间),因此一个系统支持轻量级进程的数量是有限的。
看一个简单的栗子:
- Java代码:
/**
* 如何使用内核线程来创建一个简单的多线程程序
*/public class KernelThreadExample {public static void main(String[] args) {KernelThread kernelThread = new KernelThread();kernelThread.start();}
}
- JNI头文件 (
KernelThread.h
):
#include <jni.h>
#include <pthread.h> // 用于创建线程
#include <stdio.h> // 用于输出信息// 声明本地方法
JNIEXPORT void JNICALL Java_KernelThreadExample_startNativeThread(JNIEnv *env, jobject obj);
- C/C++实现 (
KernelThread.c
):
#include "KernelThread.h"
#include <pthread.h> // 用于创建线程
#include <stdio.h> // 用于输出信息// 本地线程函数
void* threadFunction(void* arg) {printf("This is running in a native thread!\n");return NULL;
}JNIEXPORT void JNICALL Java_KernelThreadExample_startNativeThread(JNIEnv *env, jobject obj) {pthread_t threadId;pthread_create(&threadId, NULL, threadFunction, NULL); // 创建线程pthread_detach(threadId); // 分离线程,这样主程序结束时线程也会结束
}
- 编译和链接:使用gcc或g++编译C/C++文件,并链接到Java的本地库。确保指定JNI头文件。例如:
gcc -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" -shared -o libkernelThreadImpl.so KernelThread.c
- 运行Java程序:运行你的Java程序,确保已将生成的本地库(例如,
libkernelThreadImpl.so
)放在系统的库路径中或指定到java.library.path
系统属性。例如:
java -Djava.library.path=. KernelThreadExample
- 观察输出:会看到“This is running in a native thread!”的消息输出,这表明你的代码正在新的内核线程中执行。
1.2✅使用用户线程实现
在用户空间建立线程库,通过运行时系统(Run-time System)来完成线程的管理,因为这种线程的实现是在用户空间的,所以操作系统的内核并不知道线程的存在,所以内核管理的还是进程,所以这种线程的切换不需要内核操作。
这种实现方式下,进程和线程之间的关系是一对多的。
这种线程实现方式的优点是线程切换快,并且可以运行在任何操作系统之上,只需要实现线程库就行了。但是缺点也比较明显,就是所有线程的操作都需要用户程序自己处理,并且因为大多数系统调用都是阻塞的,所以一旦一个进程阻塞了,那么进程中的所有线程也会被阻塞。还有就是多处理器系统中如何将线程映射到其他处理器上也是一个比较大的问题。
1.3✅使用用户线程加轻量级进程混合实现
还有一种混合实现的方式,就是线程的创建在用户空间完成,通过线程库进行,但是线程的调度是由内核来完成的。多个用户线程通过多路复用来复用多个内核线程。这个就不展开讲了。
一、✅拓展知识仓
2.1✅内核线程有什么优点和缺点
内核线程的优点主要包括:
- 高并发性:在多处理器系统上,内核能够同时调度同一进程中的多个线程执行,从而实现高并发性。
- 资源利用率高:当一个线程被阻塞时,内核可以调度同一进程中的其他线程继续执行,从而充分利用系统资源。
- 避免用户态切换开销:内核线程在内核态运行,避免了用户态和内核态之间的切换开销。
内核线程缺点:
- 系统开销大:相对于用户线程,内核线程需要更多的系统资源,如内核栈和上下文切换时的资源。
- 不适合小规模并发:对于小规模的并发,内核线程可能不是最优的选择,因为其创建和销毁成本较高。
- 不适合低延迟场景:内核线程的上下文切换时间较长,因此不适合需要低延迟的场景。
- 受限于操作系统调度策略:内核线程的行为受限于操作系统的调度策略,这可能影响其性能和行为。
内核线程适用于需要高并发和资源利用率的应用,如服务器和大数据处理等场景。但在小规模并发、低延迟或特定性能要求的场景中,用户线程或其他并发模型可能更合适。
2.2✅内核线程和用户线程的区别
内核线程和用户线程的区别主要体现在以下几个方面:
- 创建和销毁:内核线程由操作系统内核创建和销毁,而用户线程由应用程序创建和销毁。
- 运行模式:内核线程运行在内核态,可以访问操作系统的所有资源,而用户线程运行在用户态,只能访问应用程序的资源。
- 任务类型:内核线程可以执行任何操作系统提供的服务,如文件系统、网络等,而用户线程只能执行应用程序提供的服务。
- 调度和切换:内核线程的创建和销毁需要操作系统内核的支持,而用户线程的创建和销毁由应用程序自己控制。内核线程的切换需要操作系统内核的支持,而用户线程的切换由应用程序自己控制。内核线程的调度由操作系统内核负责,而用户线程的调度由应用程序自己控制。
- 权限:内核线程始终具有最高权限,且权限不低于用户线程。用户线程的权限可以调整,但无法超过内核线程的权限。
- 效率:对于用户线程来说,如果系统调用是阻塞的,那么整个进程都会阻塞。这时需要内核线程来处理,因为内核线程是在操作系统中实现的机制,它在操作系统中有线程控制块(TCB),在发生错误或运行权限到期需要进行上下文切换时,会主动向内核发送中断信号并停止执行,将运行权限交给内核,由内核的interrupt handler进行相应的处理。
内核线程和用户线程在多个方面存在显著差异,具体选择使用哪种方式要根据具体的应用场景和需求来决定。
2.3✅内核线程有哪些应用场景
内核线程的应用场景主要包括:
- 服务器端编程:在服务器端编程中,内核线程常被用于处理大量并发的请求。由于内核线程可以同时处理多个请求,因此可以大大提高服务器的处理能力和吞吐量。
- 实时系统:在实时系统中,内核线程用于确保关键任务能够及时执行。通过使用内核线程,实时系统可以实现确定性的行为和快速的系统响应。
- 设备驱动程序:许多设备驱动程序使用内核线程来处理硬件事件或执行后台任务。这样可以将设备的操作与系统的其他部分隔离,并确保设备的正确和高效运行。
-
文件系统和网络协议:文件系统和网络协议通常使用内核线程来处理文件和网络操作。这样可以提高系统的整体性能,并确保这些操作的可靠性和高效性。
-
系统守护进程和服务:许多系统守护进程和服务使用内核线程来执行长期运行的后台任务。例如,syslog服务使用内核线程来读取并处理系统日志消息。
- 并行计算:在需要大规模并行处理的场景下,如高性能计算(HPC),内核线程可以用于实现高效的并行计算。通过将计算任务分解为多个子任务,并由多个内核线程同时执行,可以提高整体计算性能。
总之吧,内核线程在许多场景中都有广泛的应用,特别是在需要高并发、实时性、可靠性和高效的系统场景中。
2.4✅Java的线程实现
开头讲的是操作系统的线程的实现的三种方式,不同的操作系统在实现线程的时候会采用不同的机制比如windows采用的是内核线程实现的,而Solaris则是通过混合模式实现的。
而Java作为一门跨平台的编程语言,实际上他的线程的实现其实是依赖具体的操作系统的。而比较常用的windows和linux来说,都是采用的内核线程的方式实现的。
也就是说,当我们在JAVA代码中创建一个Thread的时候,其实是需要映射到提作系统的线程的具体实现的,因为常见的通过内核线程实现的方式在创建、调度时都需要进行内核参与,所以成本比较高,尽管JAVA中提供了线程池的方式来避免重复创建线程,但是依旧有很大的优化空间。而且这种实现方式意味着受机器资源的影响,平台线程数也是有限制的。
2.5✅虚拟线程
JDK 19引入的虚拟线程,是JD 实现的轻量级线程,他可以避免上下文切换带来的的额外耗费。他的实现原理其实是JDK不再是每一个线程都一对一的对应一个操作系统的线程了,而是会将多个虚拟线程映射到少量操作系统线程中,通过有效的调度来避免那些上下文切换。
而且,我们可以在应用程序中创建非常多的虚拟线程,而不依赖于平台线程的数量。这些虚拟线程是由JVM管理的,因此它们不会增加额外的上下文切换开销,因为它们作为普通Java对象存储在RAM中。
2.6 ✅虚拟线程和平台线程的区别
首先,虚拟线程总是守护线程。setDaemon (false)
方法不能将虚拟线程更改为非守护线程。所以,需要注意的是,当所有启动的非守护线程都终止时,JVM将终止。这意味着JM不会等待虚拟线程完成后才退出。
其次,即使使用setPriority()方法,虚拟线程始终具有 normal
的优先级,且不能更改优先级。在虚拟线程上调用此方法没有效果。
还有就是,虚拟线程是不支持stop()、suspend()或resume()等方法。这些方法在虚拟线程上调用时会抛出UnsupportedOperationException
异常。
2.7✅如何使用
接下来个绍一下,在JDK 19中如何使用虚拟线程。
首先,通过Thread.startVirtualThread0可以运行一个虚拟线程:
Thread.startVirtualThread(() -> {System.out.println("虚拟线程执行中...");
});
其次,通过 Thread.Builder
也可以创建虚拟线程,Thread类提供了ofPlatform()
来创建一人平台线程ofVirtual0来创建虚拟线程。
Thread.Builder platformBuilder = Thread.ofplatform().name("平台线程");
Thread.Builder virtualBuilder = Thread.ofVirtual().name("虚拟线程");Thread t1 = platformBuilder .start(() -> {...});
Thread t2 = virtualBuilder.start(() -> {...});
另外,线程池也支持了虚拟线程,可以通过 Executors.newVirtua ThreadPerlaskExecutor()
来创建康拟线程:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {Intstream.range(0, 10000).forEach(i -> {executor.submit(() -> {Thread.sleep(Duration.ofseconds(1));return i:}};});
}
但是,其实并不建议虚拟线程和线程池一起使用,因为Java线程池的设计是为了避免创建新的操作系统线程的开销,但是创建虚拟线程的开销并不大,所以其实没必要放到线程池中。
2.8 ✅性能差异
我在这里说了大半天,虚拟线程到底能不能提升性能,能提升多少呢? 我们来做个测试。
我们写一个简单的任务,在控制台中打印消息之前等待1秒:
final AtomicInteger atomicInteger = new AtomicInteger();Runnable runnable = () -> {try {Thread.sleep(Duration.ofseconds(1));} catch(Exception e) {System.out .println(e);}System.out.println("Work Done - " + atomicInteger.incrementAndGet());
});
现在,我们将从这个Runnable创建10,000个线程,并使用虚拟线程和平台线程执行它们,以比较两者的性能。
先来我们比较熟悉的平台线程的实现:
Instant start = Instant.now();try (var executor = Executors .newFixedThreadPool(100)) {for(int i = ; i < 10_000; i++) {executor.submit(runnable);}
}
Instant finish = Instant .now();
long timeElapsed = Duration.between(start, finish).toMillis();
System.out.printIn("总耗时 :" + timeElapsed);
输出结果为:
总耗时 : 102323
总耗时大概100秒左右。接下来再用虚拟线程跑一下看看。
在JDK 21中已经是正式功能了,但是在JDK 19中,虚拟线程是一个预览API,默认是禁用。所以需要使用$java–source 19–enable-preview xxjava 的方式来运行代码。
Instant start = Instant.now();try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {for(int i = ; i < 10_000; i++) {executor.submit(runnable);}
}Instant finish = Instant.now();long timeElapsed = Duration.between(start, finish).toMillis():System.out.printIn("总耗时 :" + timeElapsed);
使用 Executors.newVirtualThreadPerTaskExecutor
来创建虚拟线程,执行结果如下:
总耗时:1674
总耗时大概1.6秒左右。
100秒和1.6秒的差距,足以看出虚拟线程的性能提升还是立竿见影的。
相关文章:

【昕宝爸爸小模块】深入浅出之JDK21 中的虚拟线程到底是怎么回事(二)
➡️博客首页 https://blog.csdn.net/Java_Yangxiaoyuan 欢迎优秀的你👍点赞、🗂️收藏、加❤️关注哦。 本文章CSDN首发,欢迎转载,要注明出处哦! 先感谢优秀的你能认真的看完本文&…...

两周掌握Vue3(三):全局组件、局部组件、Props
文章目录 一、全局组件1.创建全局组件2.在main.js中注册全局组件3.使用全局组件 二、局部组件1.创建局部组件2.在另一个组件中注册、使用局部组件 三、Props1.定义一个子组件2.定义一个父组件3.效果 代码仓库:跳转 本博客对应分支:03 一、全局组件 Vue…...
Web前端篇——element-plus组件设置全局中文
背景:在使用el-date-picker组件时,发现组件中的文字默认都是英文。 设置全局中文的方法如下:(本文只介绍CDN方式) <script src"//unpkg.com/element-plus/dist/locale/zh-cn"></script> <s…...

【iOS】数据存储方式总结(持久化)沙盒结构
在iOS开发中,我们经常性地需要存储一些状态和数据,比如用户对于App的相关设置、需要在本地缓存的数据等等,本篇文章将介绍六个主要的数据存储方式 iOS中数据存储方式(数据持久化) 根据要存储的数据大小、存储数据以及…...

硬盘重新分区怎么恢复分区之前的文件?
分区是常见的故障,通常由多种原因引起。一方面,硬盘老化或者受到损坏可能会导致分区表出现问题;另一方面,用户误操作,如格式化或分区不当,也可能导致分区丢失。针对此问题,解决方法包括使用专业…...

C++每日一练(15):简单幂计算
题目描述 输入两个数a和b,求a的b次方。 输入 输入两个整数a,b(1<a<10,1<b<15)。 输出 输出一个正整数,该值<1000000000000。 输入样例 3 3 输出样例 27 参考答案 #include<bits/stdc.h&…...

扫雷游戏【可展开一片,超详细,保姆级别,此一篇足够】
一、C语言代码实现的扫雷游戏的运行 C语言实现扫雷 二、扫雷游戏的分析与设计 1.扫雷游戏的界面设计 在玩家玩扫雷的时候,它会给你一个二维的棋盘(下面的讲解都以9x9规格为例子),然后点击你想排查的坐标,若不是雷的&…...

鸿蒙开发-DevEco Studio Profiler工具进行帧率分析
Frame Profiler概述 DevEco Studio内置Profiler分析调优工具,其中Frame分析调优功能,用于录制GPU数据信息,录制完成展开之后的子泳道对应录制过程中各个进程的帧数据,主要用于深度分析应用或服务卡顿丢帧的原因。此外,…...

Google推出Telecom Jetpack库,让Android通话应用创建更简单
Google推出Telecom Jetpack库,让Android通话应用创建更简单 Telecom Jetpack库的最新Alpha版本已经推出。该库提供了多个API,以简化Android开发者创建语音和/或视频通话应用程序的过程,支持常见功能,例如接听/拒绝、音频路由等等…...

倒计时1天|解锁「PolarDB开发者大会」正确打开方式
1月17日 9:30-16:30 北京嘉瑞文化中心 PolarDB开发者大会 明天就要和大家就见面啦~ 大会参会指南现已出炉 各位开发者们,请查收~ 👇👇👇 点击 大会主页 or 扫描上方二维码 一键抵达大会官网👇 查看…...
链表-两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。 你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。 // 递归版本 class Solution {public ListNode swapPairs(ListNode head) {// base case 退出提交if(head nu…...

vue下载文件流效果demo(整理)
在 Vue 项目中,你可以使用 FileSaver.js 库来方便地下载文件流。FileSaver.js 封装了不同浏览器的下载方式,使得下载文件更加简单和兼容。以下是一个完整的示例方法: 首先,安装 FileSaver.js 库: <template>&l…...

【从0上手cornerstone3D】如何渲染一个基础的Dicom文件(含演示)
一、Cornerstone3D 是什么? Cornerstone3D官网:https://www.cornerstonejs.org/ 在线查看显示效果(加载需时间,可先点击运行),欢迎fork 二、代码示例 了解了Cornerstone是什么,有什么作用后&…...
Unity3D PVP游戏位置同步算法优化详解
在Unity3D中,PVP(Player versus Player)游戏的位置同步是一项重要的技术,它决定了游戏中玩家之间的互动体验。本文将详细介绍Unity3D PVP游戏位置同步算法的优化方法,并给出相应的技术详解和代码实现。 对啦ÿ…...
【速成】蓝桥杯嵌入式省一教程
写在前面 蓝桥杯虽然是水赛,含金量不如其他老牌竞赛那么高,但对提高自身的能力还是有一定帮助的。无论你是想混个奖项加加综测分,还是想学学单片机、嵌入式的知识,蓝桥杯电子类的嵌入式竞赛都值得参加一次。当然蓝桥杯电子类竞赛…...
通过Lambda表达式获取字段列名,以及需要注意的地方
日常开发用MyBatis Plus的lambda表达式查询,不用手写column列名,不易出错且便于改动,如下: LambdaQueryWrapper<LanguageIntercept> lambdaQueryWrapper new LambdaQueryWrapper(); lambdaQueryWrapper.eq(LanguageInterc…...

消息队列的作用与使用场景?
一、消息队列的作用 队列的主要作用是消除高并发访问高峰,加快网站的响应速度。 在不使用消息队列的情况下,用户的请求数据直接写入数据库,在高并发的情况下,会对数据库造成巨大的压力,同时也使得系统响应延迟加剧。 …...
前端常见面试题之ajax、http
文章目录 一、手写ajax请求1. get2. post3. xhr.readyState4. xhr.status5. xhr.open 二、跨域三、cookie、localStorage和sessionStorage四、http1. http常见的状态码有哪些2. http常见的header有哪些3. 什么是RestfulAPI4. 描述一下http的缓存机制5. https 一、手写ajax请求 …...

林江院长:让斜视的孩子改“斜”归正,“正视”未来
读写时跳行、不敢和别人对视、拍照时不敢看镜头......这些不便是不少斜视患儿每天都在经历的日常。 斜视是目前儿童常见的眼科疾病之一,该眼病不仅给孩子的外在形象带来影响,更重要的是会影响双眼视功能及身心健康,其危害不容小觑。 7岁男孩晓…...
windows系统Mysql备份脚本
一.背景 用的windows server 2019服务器,mysql8.0.34,还是应该每天备份一下。以前做了很多次,主要是带了2个徒弟,还是要写出来。 二.备份脚本 chcp 936 set date_tmp%date:~0,10% set datetime%date_tmp:/%%time:~0,2%%time:~3,…...
FFmpeg 低延迟同屏方案
引言 在实时互动需求激增的当下,无论是在线教育中的师生同屏演示、远程办公的屏幕共享协作,还是游戏直播的画面实时传输,低延迟同屏已成为保障用户体验的核心指标。FFmpeg 作为一款功能强大的多媒体框架,凭借其灵活的编解码、数据…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

无法与IP建立连接,未能下载VSCode服务器
如题,在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈,发现是VSCode版本自动更新惹的祸!!! 在VSCode的帮助->关于这里发现前几天VSCode自动更新了,我的版本号变成了1.100.3 才导致了远程连接出…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
【论文笔记】若干矿井粉尘检测算法概述
总的来说,传统机器学习、传统机器学习与深度学习的结合、LSTM等算法所需要的数据集来源于矿井传感器测量的粉尘浓度,通过建立回归模型来预测未来矿井的粉尘浓度。传统机器学习算法性能易受数据中极端值的影响。YOLO等计算机视觉算法所需要的数据集来源于…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...

EtherNet/IP转DeviceNet协议网关详解
一,设备主要功能 疆鸿智能JH-DVN-EIP本产品是自主研发的一款EtherNet/IP从站功能的通讯网关。该产品主要功能是连接DeviceNet总线和EtherNet/IP网络,本网关连接到EtherNet/IP总线中做为从站使用,连接到DeviceNet总线中做为从站使用。 在自动…...

第 86 场周赛:矩阵中的幻方、钥匙和房间、将数组拆分成斐波那契序列、猜猜这个单词
Q1、[中等] 矩阵中的幻方 1、题目描述 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵,其中每行,每列以及两条对角线上的各数之和都相等。 给定一个由整数组成的row x col 的 grid,其中有多少个 3 3 的 “幻方” 子矩阵&am…...

Mac下Android Studio扫描根目录卡死问题记录
环境信息 操作系统: macOS 15.5 (Apple M2芯片)Android Studio版本: Meerkat Feature Drop | 2024.3.2 Patch 1 (Build #AI-243.26053.27.2432.13536105, 2025年5月22日构建) 问题现象 在项目开发过程中,提示一个依赖外部头文件的cpp源文件需要同步,点…...

Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...