JDK19 - synchronized关键字导致的虚拟线程PINNED
JDK19 - synchronized关键字导致的虚拟线程PINNED
- 前言
- 一. PINNED是什么意思
- 1.1 synchronized 绑定测试
- 1.2 synchronized 关键字的替代
- 二. -Djdk.tracePinnedThreads的作用和坑
- 2.1 死锁案例测试
- 2.2 发生原因的推测
- 2.3 总结
前言
在 虚拟线程详解 这篇文章里面,我们详解了虚拟线程的一个执行原理和底层执行顺序。那么这里我们分享一下一个使用虚拟线程的坑点。
一. PINNED是什么意思
PINNED
指的是绑定,意思是虚拟线程无法在阻塞操作期间卸载,而被固定到其运载线程。 JEP425
给出的说明中,提到了两种发生pinned
的情况:
- 当调用的代码中被
synchronized
关键字修饰。 - 执行
native method
或foreign function
。
1.1 synchronized 绑定测试
案例代码:
public class Main {/*** 用于测试同步锁的对象*/private static volatile Object instance = new Object();/*** 用于格式化时间*/private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/*** 执行任务*/private static void runTask(int threadNum) {realRunTask(threadNum);}/*** 执行任务(加锁)* @param threadNum*/private static void runTaskWithSynchronized(int threadNum) {synchronized (instance) {realRunTask(threadNum);}}// Calendar 转 yyyy-MM-dd HH:mm:sspublic static String format(Calendar calendar) {return sdf.format(calendar.getTime());}private static void realRunTask(int threadNum) {System.out.printf("%s|Test is start ThreadNum is %s %s%n", Thread.currentThread(), threadNum, format(Calendar.getInstance()));try {Thread.sleep(1000);} catch (Exception e) {}System.out.printf("%s|Test is Over ThreadNum is %s %s%n", Thread.currentThread(), threadNum, format(Calendar.getInstance()));}private static ExecutorService getExecutorService(boolean isVirtualThread, boolean useThreadPool) {if (useThreadPool) {return new ThreadPoolExecutor(50, 50, 1, TimeUnit.MINUTES,new ArrayBlockingQueue<>(100000),isVirtualThread ? Thread.ofVirtual().factory() : Thread.ofPlatform().factory());} else {ThreadFactory factory = isVirtualThread ?Thread.ofVirtual().name("This-Test-Virtual-Thread-", 0).factory() : Thread.ofPlatform().name("This-Test-Platform-Thread-", 0).factory();return Executors.newThreadPerTaskExecutor(factory);}}/*** -Djdk.tracePinnedThreads=full* -Djdk.virtualThreadScheduler.parallelism=1* -Djdk.virtualThreadScheduler.maxPoolSize=1* -Djdk.virtualThreadScheduler.minRunnable=1** -Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=2 -Djdk.virtualThreadScheduler.minRunnable=1*/public static void main(String[] args) throws Exception{ExecutorService executorService = getExecutorService(true, false);Future task1 = executorService.submit(() -> runTaskWithSynchronized(1));Future task2 = executorService.submit(() -> runTask(2));executorService.close();task1.get();task2.get();}
}
分析:
- 我们有用于测试同步锁的对象
instance
,专门拿来给synchronized
关键字用的。 - 两种执行任务方式:一种普通的,一种加锁的。
- 执行的任务做了什么:睡眠了一秒钟,并且打印相关数据。
- 我们同时启动两个
task
,看看最终的结果是什么。
我们在启动之前,给Main函数添加一些参数:
-
-Djdk.tracePinnedThreads=full
:开启对虚拟线程的跟踪。设置为"full"
表示输出详细的虚拟线程信息,包括线程ID
、状态和执行时间等。这样被pinned
的时候,我们就可以通过打印的信息观察到了 (后面有惊喜) -
-Djdk.virtualThreadScheduler.parallelism=1
:这个参数指定了虚拟线程调度器的并行度。并行度表示同时执行虚拟线程的最大数量。在这里,设置为1表示只允许一个虚拟线程同时执行。 -
-Djdk.virtualThreadScheduler.maxPoolSize=1
:这个参数指定了虚拟线程调度器的最大线程池大小。线程池是用于存放虚拟线程的容器。在这里,设置为1表示线程池的大小为1,即最多只能容纳一个虚拟线程。 -
-Djdk.virtualThreadScheduler.minRunnable=1
:这个参数指定了虚拟线程调度器的最小可运行虚拟线程数。当虚拟线程池中的可运行线程数低于这个值时,调度器会尝试创建新的虚拟线程以填充线程池。在这里,设置为1表示最小可运行线程数为1。
我们设置可执行的线程数为1:maxPoolSize=1
-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=1 -Djdk.virtualThreadScheduler.minRunnable=1
那么此时由于最大只有一个可执行线程,因此按照逻辑顺序,应该是带有synchronized
关键字的task1
先执行,再执行task2
。而因为task1
被synchronized
关键字修饰,因此线程被pinned
:
我们设置可执行的线程数为2:maxPoolSize=2
,那么此时两个任务可以同时提交,但是task1
被synchronized
关键字修饰,因此线程同样被pinned
:
-Djdk.tracePinnedThreads=full -Djdk.virtualThreadScheduler.parallelism=1 -Djdk.virtualThreadScheduler.maxPoolSize=2 -Djdk.virtualThreadScheduler.minRunnable=1
注意:这两个返回结果的顺序是不一样的!
从上面的结果上来看,直观的结论就是:
- 如果执行代码中包含了
synchronized
关键字,那么这个线程将会被pinned
。即任务1所在的虚拟线程无法卸载,而是被固定到了运载线程。 - 哪怕两个任务是“同时”提交,也会优先将任务1(被
pinned
的线程)执行完毕,再去启动任务2。因为任务2只能等待任务1执行完毕才能够继续执行。 - 那么也就失去了异步的一个概念了。
那么针对这种情况,我们如何解决?官方建议是使用Synchronized
关键字的地方可以利用其他锁,比如重入锁来替代。
1.2 synchronized 关键字的替代
我们再写一个函数:
private static void runTaskWithReentrantLock(int threadNum) {ReentrantLock reentrantLock = new ReentrantLock();reentrantLock.lock();try {realRunTask(threadNum);} catch (Exception e) {} finally {reentrantLock.unlock();}
}
同时将maxPoolSize
重新设置为1,然后启动的时候变更如下:
public static void main(String[] args) throws Exception{ExecutorService executorService = getExecutorService(true, false);Future task1 = executorService.submit(() -> runTaskWithReentrantLock(1));Future task2 = executorService.submit(() -> runTask(2));executorService.close();task1.get();task2.get();
}
结果如下:可见,哪怕我们可执行的线程只有1个,但是两个任务也几乎是同时并发执行的。同时pinned
的情况也不复存在。
二. -Djdk.tracePinnedThreads的作用和坑
我们先来说下这个参数的作用吧。在上文中,我们使用了-Djdk.tracePinnedThreads
参数来打印虚拟线程pinned
时相关的堆栈信息。让我们非常直观的观察到pinned
的行为。
那么试想一下,我们为了去使用虚拟线程这个新特性,而进行JDK
的升级。这个升级难以避免的是带来一定的风险。例如上文的synchronized
关键字。它的存在可能导致你的虚拟线程无法被卸载,而进入pinned
状态。那么,你的代码又有哪些隐藏的风险需要你关注呢?
- 你的代码中是否有显式地使用
synchronized
关键字? - 你引入的外部第三方依赖中,内部操作是否同样地使用了
synchronized
关键字?
前者我们可以通过全局搜索,自己去在项目里面解决,但是要命的是后者,你很难做到全面排查所有的第三方依赖对synchronized
关键字的使用情况。那么我们就可以增加这个参数去打印可能发生的pinned
情况,一旦有,我们就可以通过堆栈信息去定位代码,然后解决。
-Djdk.tracePinnedThreads=full
但是这个情况仅仅适用于本地开发或者是测试环境的灰度阶段,并不适合发到生产。为什么呢?因为这个VM参数同样可能导致虚拟线程不可用,发生死锁。这是本文想分享的第二个重点。
2.1 死锁案例测试
添加两个依赖:
<dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId><version>4.5.13</version>
</dependency>
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version>
</dependency>
贴出代码:
public class LockTest {/*** 平台线程数*/static int PLATFORM_THREAD_COUNT;/*** 虚拟线程数*/static int VIRTUAL_THREAD_COUNT;static CloseableHttpClient client;public static void main(String[] args) throws Exception {PLATFORM_THREAD_COUNT = 1;VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT + 1;// 替换为这个即可解决死锁// VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT;// 初始化apache http clientclient = initClient();// 设置虚拟线程池大小,最大线程数,最小可运行线程数 为平台线程数String strSize = Integer.toString(PLATFORM_THREAD_COUNT);System.setProperty("jdk.virtualThreadScheduler.parallelism", strSize);System.setProperty("jdk.virtualThreadScheduler.maxPoolSize", strSize);System.setProperty("jdk.virtualThreadScheduler.minRunnable", strSize);// 启动测试test();}public static void test() throws Exception {// 设置栅栏数为虚拟线程数CountDownLatch countDownLatch = new CountDownLatch(VIRTUAL_THREAD_COUNT);// 启动对应数量的虚拟线程任务for (int j = 0; j < VIRTUAL_THREAD_COUNT; j++) {Thread.ofVirtual().start(() -> apachePoolingHttpClient(client, countDownLatch));}// 如果任务没有执行完毕,等待,会循环打印等待信息while (countDownLatch.getCount() != 0) {System.out.println("waiting " + countDownLatch.getCount());Thread.sleep(2000);}// 只有虚拟线程执行完毕,才会执行下面的代码System.out.println("end success");}/*** 初始化apache http client,没什么好看的** @return*/private static CloseableHttpClient initClient() {PoolingHttpClientConnectionManager poolingConnManager = new PoolingHttpClientConnectionManager();poolingConnManager.setMaxTotal(PLATFORM_THREAD_COUNT);return HttpClients.custom().setConnectionManager(poolingConnManager).build();}/*** apache http client 发送请求,关注点在最后一行代码,执行IO完毕,会调用countDownLatch.countDown(),表示当前虚拟线程执行完毕*/private static void apachePoolingHttpClient(CloseableHttpClient client, CountDownLatch countDownLatch) {HttpGet request = new HttpGet("https://www.google.com");try (CloseableHttpResponse execute = client.execute(request)) {StatusLine statusLine = execute.getStatusLine();System.out.println(statusLine.getStatusCode());} catch (Throwable e) {throw new RuntimeException(e);} finally {countDownLatch.countDown();}}
}
分析如下:
- 我们设置平台线程数为1个,虚拟线程数为2个。然后启动两个虚拟线程任务。
- 启动任务之前,我们初始化了一个栅栏
CountDownLatch
,总数为2。如果这个数量不为0,那么就会循环打印waiting
信息。 - 每个任务会进行网络
IO
,等待IO
结束的时候,会触发countDownLatch.countDown();
- 直到两个任务都执行完毕,才会停止循环,打印
end success
运行结果如下:无限打印2,可见发生了死锁。
值得注意的是:
- 虚拟线程发生的死锁,常规的检测工具是检测不出来的。
jstack
和jconsole
我都试过了。 - 我们只能从结果的现象发现:两个虚拟线程都无法结束,这个循环会永远的进行下去。
2.2 发生原因的推测
而这个打印堆栈的功能和-Djdk.tracePinnedThreads
这个VM
参数息息相关。
我们全局搜索这个参数:
private static final int TRACE_PINNING_MODE = tracePinningMode();private static int tracePinningMode() {String propValue = GetPropertyAction.privilegedGetProperty("jdk.tracePinnedThreads");if (propValue != null) {if (propValue.length() == 0 || "full".equalsIgnoreCase(propValue))return 1;if ("short".equalsIgnoreCase(propValue))return 2;}return 0;
}
可以看到,只要设置了这个参数,这个返回值就是大于0的。还记得我在 虚拟线程详解 这篇文章里面提到的VThreadContinuation
吗。那么我们再看看虚拟线程底层对Continuation
的封装:这里面重写了一个onPinned
函数,也就是说,发生pinned
的时候,打印相关的堆栈信息
private static class VThreadContinuation extends Continuation {VThreadContinuation(VirtualThread vthread, Runnable task) {super(VTHREAD_SCOPE, () -> vthread.run(task));}@Overrideprotected void onPinned(Continuation.Pinned reason) {if (TRACE_PINNING_MODE > 0) {boolean printAll = (TRACE_PINNING_MODE == 1);PinnedThreadPrinter.printStackTrace(System.out, printAll);}}
}
我们往下跟进:
static void printStackTrace(PrintStream out, boolean printAll) {List<LiveStackFrame> stack = STACK_WALKER.walk(s ->s.map(f -> (LiveStackFrame) f).filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class).collect(Collectors.toList()));// find the closest frame that is causing the thread to be pinnedstack.stream().filter(f -> (f.isNativeMethod() || f.getMonitors().length > 0)).map(LiveStackFrame::getDeclaringClass).findFirst().ifPresent(klass -> {int hash = hash(stack);Hashes hashes = HASHES.get(klass);synchronized (hashes) {// print the stack trace if not already seenif (hashes.add(hash)) {printStackTrace(stack, out, printAll);}}});
}
private static void printStackTrace(List<LiveStackFrame> stack,PrintStream out,boolean printAll) {out.println(Thread.currentThread());for (LiveStackFrame frame : stack) {var ste = frame.toStackTraceElement();int monitorCount = frame.getMonitors().length;if (monitorCount > 0 || frame.isNativeMethod()) {out.format(" %s <== monitors:%d%n", ste, monitorCount);} else if (printAll) {out.format(" %s%n", ste);}}
}
看到没,上面有一个synchronized
关键字,里面的代码也是一个IO
打印。
- 结合上下文来看,我们知道,在虚拟线程中,如果有
IO
阻塞,那么Loom
会调用park()
进行yield
调用。 - 我们假设虚拟线程A抢到了锁。然后调用了
IO
相关的函数,因此进入yield
(第一点)。 - 而众所周知,调用
yield
是不会释放锁的。那么虚拟线程B抢不到锁,由于synchronized
关键字的作用,状态进入pinned
。导致无法卸载,固定在运载线程。 - 那么运载线程被占用,卡在这,所以程序永远无法执行完毕。
最后,如果把下面的这行代码:
VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT + 1;
替换成:
VIRTUAL_THREAD_COUNT = PLATFORM_THREAD_COUNT;
就不会产生死锁的情况了
执行结果如下:
2.3 总结
进行虚拟线程的代码改造的时候,我们要注意一个点:
synchronized
关键字对虚拟线程pinned
的副作用,我们要考虑到如何兼容和更改,可以使用重入锁进行替代。- 由于这个
synchronized
关键字我们难以排查完全,我们可以增加-Djdk.tracePinnedThreads
参数信息打印pinned
发生时候的堆栈信息,助于我们排查,但是这个操作建议只在本地或者测试环境进行。因为他可能会导致你的程序发生死锁。 - 建议测试环境进行灰度测试,保证
pinned
的情况不再发生的时候,可以再发到生产环境进行灰度。
最后的最后,附上堆栈信息的获取方式:
- 输入命令
jps
,找到你自己运行程序的pid
。 - 输入
jstack
命令:jstack -l 你自己的pid > 1.txt
。这样就可以在这个文本中看到发生死锁时候的堆栈信息了。
相关文章:

JDK19 - synchronized关键字导致的虚拟线程PINNED
JDK19 - synchronized关键字导致的虚拟线程PINNED 前言一. PINNED是什么意思1.1 synchronized 绑定测试1.2 synchronized 关键字的替代 二. -Djdk.tracePinnedThreads的作用和坑2.1 死锁案例测试2.2 发生原因的推测2.3 总结 前言 在 虚拟线程详解 这篇文章里面,我们…...

用msys2安装verilator并用spinal进行仿真
一 参考 SpinalHDL 开发环境搭建一步到位(图文版) - 极术社区 - 连接开发者与智能计算生态 (aijishu.com)https://aijishu.com/a/1060000000255643Setup and installation of Verilator — SpinalHDL documentation...

【ARM64 常见汇编指令学习 13 -- ARM 汇编 ORG 伪指令学习】
文章目录 ARM ORG 指令介绍UEFI 中对 ORG 指令的使用 ARM ORG 指令介绍 在ARM汇编中,"org"是一个汇编器伪指令,用于设置下一条指令的装入地址。"org"后面跟着的是一个表达式,这个表达式的值就是下一条指令的装入地址。如…...

Vue使用QuillEditor富文本编辑器问题记录
1.内容绑定的问题 绑定内容要使用 v-model:content"xxx" 的形式。 2.设置字体字号 字体以及字号大小的设置需要先注册。 <script> import { QuillEditor,Quill } from vueup/vue-quill import vueup/vue-quill/dist/vue-quill.snow.css; // 设置字体大小 c…...

spring AOP学习
概念 面向切面编程横向扩展动态代理 相关术语 动态代理 spring在运行期,生成动态代理对象,不需要特殊的编译器 Spring AOP的底层就是通过JDK动态代理或者CGLIb动态代理技术为目标Bean执行横向织入 目标对象实现了接口,spring使用JDK的ja…...

16.M端事件和JS插件
16.1移动端 移动端也有自己独特的地方 ●触屏事件touch (也称触摸事件),Android 和I0S都有。 ●touch对象代表一个触摸点。触摸点可能是一根手指,也可能是一根触摸笔。触屏事件可响应用户手指(或触控笔)对屏幕或者触控板操作。 ●常见的触屏事件如下: …...

Zebec APP:构建全面、广泛的流支付应用体系
目前,流支付协议 Zebec Protocol 基本明确了生态的整体轮廓,它包括由其社区推动的模块化 Layer3 构架的公链 Nautilus Chain、流支付应用 Zebec APP 以及 流支付薪酬工具 Zebec payroll 。其中,Zebec APP 是原有 Zebec Protocol 的主要部分&a…...

Spark 3.1.1 遇到的 from_json regexp_replace组合表达式慢问题的解决
背景 目前公司在从spark 2.4.x升级到3.1.1的时候,遇到了一类SQL极慢的情况,该SQL的如下(只列举了关键的): select device_personas.* from(selectdevice_id, ads_id, from_json(regexp_replace(device_personas, (?<(\\{|,))"devic…...
Docker 容器常用的命令和操作
1.容器操作 - 运行容器: docker run [OPTIONS] IMAGE [COMMAND] [ARG...] 示例: docker run -it --rm ubuntu /bin/bash - 查看正在运行的容器: docker ps [OPTIONS] 示例: docker ps -a - 停止容器: docker stop CONTAINER [CONTAINER...] 示…...

iTOP-RK3568开发板Windows 安装 RKTool 驱动
在烧写镜像之前首先需要安装 RKTool 驱动。 RKTool 驱动在网盘资料“iTOP-3568 开发板\01_【iTOP-RK3568 开发板】基础资料 \02_iTOP-RK3568 开发板烧写工具及驱动”路径下。 驱动如下图所示: 解压缩后,进入文件夹,如下图所示:…...
nginx rtmp http_flv直播推流
安装配置nginx yum install epel-release -y sudo rpm -Uvh http://li.nux.ro/download/nux/dextop/el7/x86_64/nux-dextop-release-0-5.el7.nux.noarch.rpm yum install ffmpeg ffmpeg-devel -y yum install gcc -y yum install pcre pcre-devel -y yum install openssl open…...

Day50 算法记录| 动态规划 17(子序列)
这里写目录标题 647. 回文子串516.最长回文子序列总结 647. 回文子串 1.动态规划和2.中心扩展 这个视频是基于上面的视频的代码 方法1:动态规划 布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串,如…...

RabbitMQ:概念和安装,简单模式,工作,发布确认,交换机,死信队列,延迟队列,发布确认高级,其它知识,集群
1. 消息队列 1.0 课程介绍 1.1.MQ 的相关概念 1.1.1.什么是MQ MQ(message queue:消息队列),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是message 而已,还是一种跨进程的通信机制…...

小研究 - 基于解析树的 Java Web 灰盒模糊测试(二)
由于 Java Web 应用业务场景复杂, 且对输入数据的结构有效性要求较高, 现有的测试方法和工具在测试Java Web 时存在测试用例的有效率较低的问题. 为了解决上述问题, 本文提出了基于解析树的 Java Web 应用灰盒模糊测试方法. 首先为 Java Web 应用程序的输入数据包进行语法建模创…...
对于现有的分布式id发号器的思考 id生成器 雪花算法 uuid
在工作过程中接触了很多id生成策略,但是有一些问题 雪花id 强依赖时钟,对于时钟回拨无法很好解决 tinyid 滴滴开源,依赖mysql数据库,自增,无业务属性 uuid 生成是一个字符串没有顺序,数据库索引组织数据…...

jmeter中json提取器,获取多个值,并通过beanshell组成数组
jmeter中json提取器介绍 特别说明:**Compute concatenation var(suffix_ALL)😗*如果找到许多结果,则插件将使用’ , 分隔符将它们连接起来,并将其存储在名为 _ALL的var中 json提取器调试 在查看结果树中选择JSON Pat…...

通过nvm工具快捷切换node.js版本、以及nvm的安装
使用nvm可以实现多个Node.js版本之间切换 步骤目录: 先卸载掉本系统中原有的node版本 去github上下载nvm安装包 安装node 常用的一些nvm命令 1、先卸载掉本系统中原有的node版本 2、去github上下载nvm安装包 https://github.com/coreybutler/nvm-windows/re…...

企业如何搭建矩阵内容,才能真正实现目的?
当下,新媒体矩阵营销已成为众多企业的营销选择之一,各企业可以通过新媒体矩阵实现扩大品牌声量、维持用户关系、提高销售业绩等不同的目的。 而不同目的的矩阵,它的内容运营模式会稍有差别,评价体系也会大不相同。 企业在运营某类…...

Arduino驱动MQ5模拟煤气气体传感器(气体传感器篇)
目录 1、传感器特性 2、硬件原理图 3、驱动程序 MQ5气体传感器,可以很灵敏的检测到空气中的液化气、天然气、煤气等气体,与Arduino结合使用,可以制作火灾液化气、天然气、煤气泄露报警等相关的作品。 1、传感器特性 MQ5用于消费和工业行业中气体泄漏检测设备,该传感器适…...

Mongodb安装(Centos7)
1. 下载 MongoDB: The Developer Data Platform | MongoDB 2. 安装 上传至服务器 解压 tar -zxvf mongodb-linux-x86_64-rhel70-5.0.19.tgz 移动 mv mongodb-linux-x86_64-rhel70-5.0.19 /usr/local/mongodb 3. 配置 vim /etc/profile # set mongodb configuration expor…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(十一)
下载buildroot https://buildroot.org/download.html下载交叉工具链 使用ST官方交叉工具链的话,在buildroot配置外部工具会有问题,所以直接使用正点原子的交叉编译工具 buildroot构建根文件系统 - 参考正点原子 配置 buildroot tar -vxf buildroot-20…...

国产高云FPGA实现视频采集转UDP以太网输出,FPGA网络摄像头方案,提供2套Gowin工程源码和技术支持
目录 1、前言工程概述免责声明 2、相关方案推荐我已有的所有工程源码总目录----方便你快速找到自己喜欢的项目国产高云FPGA基础教程国产高云FPGA相关方案推荐我这里已有的以太网方案 3、设计思路框架工程设计原理框图输入Sensor之-->OV7725摄像头输入Sensor之-->OV5640摄…...
MCP(Model Context Protocol)与提示词撰写
随着大模型(LLM)在复杂任务中的普及,如何让模型高效调用外部工具和数据成为关键挑战。传统函数调用(Function Calling)依赖开发者手动封装 API,而 MCP(Model Context Protocol) 通过…...
【联网玩具】EN 18031欧盟网络安全认证
在当今数字化时代,带联网功能的玩具越来越受到孩子们的喜爱,它们为儿童带来了前所未有的互动体验和学习机会。然而,随着这类玩具的普及,网络安全问题也日益凸显。为了保障儿童使用这类玩具时的安全与隐私,欧盟出台了 E…...
csharp基础....
int[][] jaggedArray new int[3][]; jaggedArray[0] new int[] { 1, 2 }; jaggedArray[1] new int[] { 3, 4, 5 }; jaggedArray[2] new int[] { 6, 7, 8, 9 }; 嵌套 反转和排序 List<int> list new List<int> { 1, 2, 3, 4, 5 }; list.Reverse(); Cons…...

解决pycharm同一个文件夹下from *** import***仍显示No module named
1、,from ***import *,同文件夹中已有.py文件但是仍然报错No module named 原因是因为pycharm没有把文件夹设置为根目录,只需要在文件夹的上一级设置为根目录即可,测试过如果仅仅将当前的文件夹设置仍然报错,如果把最上…...

Python60日基础学习打卡Day46
一、 什么是注意力 注意力机制的由来本质是从onehot-elmo-selfattention-encoder-bert这就是一条不断提取特征的路。各有各的特点,也可以说由弱到强。 其中注意力机制是一种让模型学会「选择性关注重要信息」的特征提取器,就像人类视觉会自动忽略背景&…...

当SAP系统内计划订单转换为生产订单时发生了什么?
【SAP系统研究】 #SAP #计划订单 #生产订单 #采购申请 一、关于计划订单的一点疑惑 曾经对SAP为什么会有计划订单,是感到很疑惑的。 这个界面简单,配置点也不多,能被随意“摆布”,一旦要变形就消失得无影无踪的计划订单,why? 但是,再次重新审视过之后,才发现它其实…...

图上合成:用于大型语言模型持续预训练的知识合成数据生成
摘要 大型语言模型(LLM)已经取得了显著的成功,但仍然是数据效率低下,特别是当学习小型,专业语料库与有限的专有数据。现有的用于连续预训练的合成数据生成方法集中于文档内内容,而忽略了跨文档的知识关联&a…...
船舶事故海上搜救VR情景演练全场景 “复刻”,沉浸式救援体验
船舶事故海上搜救 VR 情景演练系统的一大核心优势,便是能够全场景 “复刻” 海上事故,为使用者带来沉浸式的船舶事故海上搜救 VR 情景演练体验。 在船舶事故海上搜救 VR 情景演练的事故场景模拟方面,系统几乎涵盖了所有常见的船舶事故类型。…...