Java 并发编程归纳总结(可重入锁 | JMM | synchronized 实现原理)
1、锁的可重入
一个不可重入的锁,抢占该锁的方法递归调用自己,或者两个持有该锁的方法之间发生调用,都会发生死锁。以之前实现的显式独占锁为例,在递归调用时会发生死锁:
public class MyLock implements Lock {/* 仅需要将操作代理到Sync上即可*/private final Sync sync = new Sync();private final static class Sync extends AbstractQueuedSynchronizer {// 判断处于独占状态@Overrideprotected boolean isHeldExclusively() {return getState() == 1;}// 获得锁@Overrideprotected boolean tryAcquire(int i) {if (compareAndSetState(0, 1)) {// 设置占有独占锁的线程setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}// 释放锁@Overrideprotected boolean tryRelease(int i) {if (getState() == 0) {throw new IllegalMonitorStateException();}setExclusiveOwnerThread(null);setState(0);return true;}// 返回一个Condition,每个condition都包含了一个condition队列public Condition newCondition() {return new ConditionObject();}}@Overridepublic void lock() {System.out.println(Thread.currentThread().getName() + " ready get lock");sync.acquire(1);System.out.println(Thread.currentThread().getName() + " already got lock");}@Overridepublic void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}@Overridepublic boolean tryLock() {return sync.tryAcquire(1);}@Overridepublic boolean tryLock(long timeout, TimeUnit timeUnit) throws InterruptedException {return sync.tryAcquireNanos(1, timeUnit.toNanos(timeout));}@Overridepublic void unlock() {System.out.println(Thread.currentThread().getName() + " ready release lock");sync.release(1);System.out.println(Thread.currentThread().getName() + " already released lock");}@Overridepublic Condition newCondition() {return sync.newCondition();}
}
测试代码:
public class Test {private static MyLock lock = new MyLock();private static class TestThread implements Runnable {public TestThread() {}@Overridepublic void run() {System.out.println(Thread.currentThread().getName());try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}reenter(3);}public void reenter(int level) {lock.lock();try {System.out.println(Thread.currentThread().getName() + ":递归层级:" + level);if (level == 0) return;reenter(level - 1);} finally {lock.unlock();}}}public static void main(String[] args) {for (int i = 0; i < 3; i++) {Thread thread = new Thread(new TestThread(new JavaBean(0)));thread.start();}}
}
输出结果:
Thread-2 ready get lock
Thread-0 ready get lock
Thread-2 already got lock
Thread-1 ready get lock
Thread-2:递归层级:3
Thread-2 ready get lock
代码停在这里发生死锁,原因是 Thread-2 已经拿到了锁,在递归到下一层时,还要获取 lock,但是 MyLock 没实现可重入,使得它在执行 tryAcquire() 的原子操作 compareAndSetState(0,1) 时一直不成功,因为期望值此时已经由 0 变成了 1。所以这里需要实现可重入锁。
想要实现可重入的锁,需要让 state 作为锁的计数器:
// 获得锁@Overrideprotected boolean tryAcquire(int i) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;} else if (getExclusiveOwnerThread() == Thread.currentThread()) {setState(getState() + 1);return true;}return false;}// 释放锁@Overrideprotected boolean tryRelease(int i) {if (getExclusiveOwnerThread() != Thread.currentThread()) {throw new IllegalMonitorStateException();}if (getState() == 0) {throw new IllegalMonitorStateException();}setState(getState() - 1);if (getState() == 0) {setExclusiveOwnerThread(null);}return true;}
state 作为持有这个锁的线程的数量,锁被持有了几次,就要相应的释放几次。
2、Java 内存模型(JMM)
上图中工作内存和主内存是两个抽象的概念,不是真实存在的实体,它们可以是 CPU 寄存器、CPU 中的高速缓存,甚至是主内存 RAM 的一部分。
线程在执行计算工作时,会把需要用到的变量从主内存拷贝到自己的工作内存中。线程不能直接操作主内存中的数据,也不能访问其它线程工作内存。这样的内存模型使得线程执行过程中面临两个问题:可见性与原子性。
2.1 可见性与原子性
可见性
是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
线程对变量的所有操作都必须在工作内存中进行,不能直接读写主内存中的变量。对于共享变量 V,多个线程先是在自己的工作内存,之后再同步到主内存。但同步动作并不会及时的刷到主存中,而是会有一定时间差。这个时候线程 A 对变量 V 的操作对于线程 B 而言就不具备可见性了。
要解决共享对象可见性这个问题,可以使 用volatile 关键字或者是加锁。
原子性
即一个操作或者多个操作要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
CPU 资源的分配都是以线程为单位的。任务切换大多数是在时间片段结束以后,当然也可以在任何一条 CPU 指令执行完之后(是 CPU 指令而不是某种高级语言的一个语句,如 Java 中的 count++ 至少需要三条 CPU 指令才能完成),这也可能导致线程安全问题。
举个例子,假如两个线程都执行语句 count = count + 1,如图所示:
线程 A、B 都把 count 的初值 0 从主内存拷贝到自己的工作内存中开始执行 count + 1 的操作,都得到结果 1 再把副本值同步到主内存中。明明进行了两次计算,但是得到的却是计算了一次的结果,这是因为两个线程对于 count 的操作是互不可见的,彼此不知道对 count 的操作。
上述问题发生的原因是未能保证线程操作的可见性,可以使用 volatile 关键字或者是加锁解决可见性问题。
但是使用 volatile 修饰 count 后问题仍没有解决,原因就是 count = count + 1 并不是一个原子操作,完全有可能在执行完 count + 1 之后,赋值给 count 之前,CPU 进行上下文切换到其它线程执行完整个 count = count + 1 并将结果同步回内存,最后切换会原线程继续执行的情况,这就是原子性问题。
2.2 volatile 关键字
volatile 是 Java 并发编程包中最轻量级的一个同步工具。
使用 volatile 关键字修饰一个变量,会强迫线程每次在计算该变量之前从主内存中拿最新的变量值,并且要求计算完成后立即将新的变量值同步到主内存中。
可以把对 volatile 变量的单个读/写,看成是使用同一个锁对这些单个读/写操作做了同步。如:
public class Volatile {volatile int i;// 单个 volatile 变量的读public int getI() {return i;}// 单个 volatile 变量的写public void setI(int i) {this.i = i;}private void inc() {// Non-atomic operation on volatile field 'i'i++; // 复合(多个)volatile 变量的读/写}
}
等价于:
public class Volatile {int i;// 单个 volatile 变量的读public synchronized int getI() {return i;}// 单个 volatile 变量的写public synchronized void setI(int i) {this.i = i;}private void inc() {// 调用同步读int temp = getI(); // 普通写,可能在执行这一步之前发生线程切换导致 volatile 修饰的变量发生线程安全问题temp = temp + 1;// 调用同步写setI(temp);}
}
可见 volatile 只能保证对变量的单个操作的线程安全,但像 i++ 这种复合操作,volatile 则不能保证其线程安全。
因此 volatile 变量自身具有以下特性:
- 可见性:对一个 volatile 变量的读,总是能看到任意线程对这个 volatile 变量最后的写入。
- 原子性:对任意单个 volatile 变量的读/写具有原子性,但类似于 i++ 这种复合操作不具有原子性。
volatile 适用的场景有:
- 一个线程写,多个线程读。(写线程能立即将结果写回内存,而读线程能拿到变量在内存中最新的值。否则写线程的结果可能并不是立即写回内存的,导致读线程拿到的变量不是最新)
- 多个线程写,但是各个线程写的值没有关联(count = 5 这种直接赋值是没有关联的,但是像 count = count +1 这种基于 count 原始值的认为是有关联)。
volatile 还有一个功能就是抑制重排序。
重排序是指在现代 CPU 中同一时刻可以执行多条指令,可能会造成实际执行的代码顺序与编写的顺序不同的情况。例如:
do(...) {int a = 5; // 1int b = 10; // 2int t;if (b == 5) {t = b;}}
指令重排后可能后编写的语句会先被执行,即便 b == 5
的条件还未满足,也先执行 t = b
,只不过对于这种条件语句可能会先存入重排序缓冲区中,等到 b == 5
满足时再从缓冲中取出执行。重排序在单线程中是不会出现乱序问题的,但是多线程则可能会出现。如果用 volatile 修饰某个变量,就不会对其进行重排序。
Intel CPU 可以有十级流水线(即 CPU 可以在同一时刻执行十条指令),Android 芯片的 ARM 架构也可达到三级流水线。
volatile 的实现原理是被 volatile 修饰的共享变量进行读写操作的时候会使用 CPU 提供的 Lock 前缀指令。该指令的作用是:
- 将当前处理器缓存行的数据写回到系统内存。
- 写回内存的操作会使其它 CPU 里缓存了该内存地址的数据无效。
以上是对 volatile 关键字的介绍。最后我们再回头看下 count = count + 1 的问题的解决方案:
- 使用 volatile 关键字搭配 CAS 操作,前者保证可见性,后者保证原子性。实际上 JDK 中很多同步操作都是使用 volatile + CAS 来代替 synchronized。
- 直接用锁,synchronized、Lock…
3、synchronized 实现原理
3.1 monitorenter 和 monitorexit 指令
底层是使用 monitorenter 和 monitorexit 指令实现的。对于使用了 synchronized 同步代码块的代码:
public class IncTest {private int count;// public 才能被 javap 反编译出来public int inc() {synchronized (this) {return count++;}}
}
编译后使用 javap -v IncTest.class 命令反编译,会看到 inc 方法的汇编指令:
真正执行 count++ 操作的指令是在第4行~第15行,而第3行的 monitorenter 与第16行的 monitorexit 则分别是获取锁和释放锁的指令。这两条指令是由编译器插入的。而使用同步方法时:
public class IncTest {private int count;// public 才能被 javap 反编译出来public synchronized int inc() {return count++;}
}
其汇编指令为:
同步方法的汇编指令没有显式的 monitorenter 和 monitorexit 指令,但是在方法的 flags 上能看到多出了一个 ACC_SYNCHRONIZED,在运行时还是用到了 monitorenter 和 monitorexit 指令,只不过无法在字节码指令上体现出来。
总结一下:
- monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 是插入到方法结束处和异常处。
- 每个 monitorenter 必须有对应的 monitorexit 与之配对。
- 任何对象都有一个 monitor 与之关联。
3.2 锁的存放位置与锁升级
Java 对象在内存中由三部分组成:对象头、实例数据和对齐填充字节。synchronized 锁就存放在对象头中,它由三部分组成:Mark Word、指向类的指针(也称 KlassPoint)和数组长度(只有数组对象才有):
它们的长度与虚拟机位数保持一致,以 32 位为例,Mark Word 的存储内容是这样的:
分代年龄是指对象经历过 GC 的次数。堆内存至少会被划分成两部分,一部分存放新生代对象,一部分存放老年代对象。JVM 默认一个新生代对象经历过15次 GC 还没有被回收,就认为该对象是一个需要长期储存的对象,于是就把它移入堆内存的老年代存放区。
我们都知道 synchronized 同步锁是一个重量级的锁,拿锁失败的线程会发生上下文切换被阻塞,直到拿到锁后又发生上下文切换由阻塞状态变成运行状态。因为上下文切换的耗时相对于 CPU 执行指令的时间是非常耗时的,一次上下文切换需要大概5000~20000个单位时间,在3~5毫秒左右,而一个1.6G的 CPU 执行一条指令耗时0.6纳秒,对于一个100条指令的任务,CPU 的执行时间也就仅仅在0.6毫秒左右。因此,如果使用 synchronized 执行一个较轻量级的任务,被阻塞等待的时间远远超过了执行任务本身所需的时间。
为了对上述情况做出优化,从 JDK 1.6 开始出现了锁升级的概念,意思是说,一个 synchronized 锁在 Mark Word 中的状态不是一成不变的,会根据任务的量级对锁的量级逐步提升,即无锁状态->偏向锁状态->轻量级锁状态->重量级锁状态(锁的四种状态):
3.2.1 偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得(统计发现),为了让线程获得锁的代价更低而引入了偏向锁(锁总是会倾向于分配给第一次拿到这个锁的线程)。无竞争时不需要进行 CAS 操作来加锁和解锁,而是直接把锁给到当前线程。但是一旦发生多个线程间的资源竞争,就要把偏向锁升级为轻量级锁,在升级之前,要先撤销偏向锁。
偏向锁撤销时中有一个 Stop the World 现象。Stop the World 是指:
在新生代进行的GC叫做minor GC,在老年代进行的GC都叫major GC,Full GC同时作用于新生代和老年代。在垃圾回收过程中经常涉及到对对象的挪动(比如上文提到的对象在Survivor 0和Survivor 1之间的复制),进而导致需要对对象引用进行更新。为了保证引用更新的正确性,Java将暂停所有其他的线程,这种情况被称为“Stop-The-World”,导致系统全局停顿。Stop-The-World对系统性能存在影响,因此垃圾回收的一个原则是尽量减少“Stop-The-World”的时间。
引用自 JVM学习(7)Stop-The-World
看上图,线程2在撤销线程1的偏向锁时,需要修改线程1工作内存中的相关数据,在修改之前要停止线程1的执行,否则线程2无法修改。因此这里也是发生了 Stop the World 现象,由于它会停止其它线程,因此在有多个线程竞争资源时,是不推荐使用偏向锁的。
3.2.2 轻量级锁
轻量级锁通过 CAS 操作来加锁和解锁。其中的自旋锁借鉴了 CAS 的思想,不会阻塞没有拿到锁的线程,而是让其自旋。假如获取到锁的那个线程执行速度很快,那么自旋中的线程也可能很快就拿到了锁,这样能节省出两次上下文切换的时间。
但是自旋是占用 CPU 在不停的循环执行检测的,倘若线程任务中有访问服务器之类的重量级操作,如果还是一直不停的自旋,就使得 CPU 不能充分的利用。因此又产生了适应性自旋锁,它会根据算法决定自旋的时间/次数,一般这个时间就是一次上下文切换的时间。因为引入轻量级锁的目的就是通过自旋节省掉使用重量级锁时产生的上下文切换的时间,如果自旋时间已经超过上下文切换时间,那么自旋也就没有意义了,此时就要把轻量级锁膨胀为重量级锁。
锁只能升级,不能降级。
相关文章:
Java 并发编程归纳总结(可重入锁 | JMM | synchronized 实现原理)
1、锁的可重入 一个不可重入的锁,抢占该锁的方法递归调用自己,或者两个持有该锁的方法之间发生调用,都会发生死锁。以之前实现的显式独占锁为例,在递归调用时会发生死锁: public class MyLock implements Lock {/* 仅…...
Git 笔记
设置Git的user name和email $ git config --global user.name "bread" $ git config --global user.email "1234567890qq.com"因为Git是分布式版本控制系统,所以需要填写用户名和邮箱作为一个标识。git config --global 参数,有了这…...
蒟蒻编程日志
ORZ (用于记录你这个“人”是不是真的,也就是说CSDN的流量是否属合适) 2025/4/14 21:25 开坑 前言 2024/10/26:CSP-J 260pts,CSP-S 45pts。 2025/3/1:%你赛 180pts rk34 寄!这就是不认真的…...
深入GoFrame框架:GToken的优势、实践与踩坑经验分享
一、引言 近年来,Go语言凭借其简洁的语法、高并发性能和强大的标准库,在后端开发领域迅速崛起。从微服务到企业级应用,Go的生态圈正在蓬勃发展,吸引了越来越多的开发者投入其中。在这个生态中,框架的选择往往决定了项…...
MODBUS RTU调试助手使用方法详解
一、软件简介 485调试助手是一款常用的串口通信调试工具,专门用于RS-485总线设备的测试、调试和通信监控。它支持多种串口参数设置,提供数据收发功能,是工业现场调试的必备工具之一。 二、软件安装与启动 1. 系统要求 Windows 7/10/11操作…...
【专利信息服务平台-注册/登录安全分析报告】
前言 由于网站注册入口容易被黑客攻击,存在如下安全问题: 暴力破解密码,造成用户信息泄露短信盗刷的安全问题,影响业务及导致用户投诉带来经济损失,尤其是后付费客户,风险巨大,造成亏损无底洞…...
我用 Appuploader绕过 Mac,成功把 iOS 应用上线了 App Store
我以前总觉得,iOS 上架是 macOS Xcode 专属的领域。直到最近项目必须要上架 iOS,团队却没人用 Mac,只能临时组建了一套“跨平台上架流程”。 这篇文章记录我这个“非典型 iOS 开发者”是如何绕开传统 Xcode 流程,借助一系列工具…...
Mac上安装运行SynthTIGER
1. 确保已安装 Python 环境 SynthTIGER 需要 Python 3.6。如果你的 Mac 没有安装 Python: 推荐通过 Homebrew 安装: brew install python 或从 Python 官网 下载安装。 验证安装: python3 --version pip3 --version 2. 安装 SynthTIGER…...
从代码学习深度学习 - 实战Kaggle比赛:狗的品种识别(ImageNet Dogs)PyTorch版
文章目录 前言1. 获取和整理数据集1.1 读取标签1.2 整理数据集:划分训练集和验证集1.3 整理测试集1.4 执行数据重组 2. 图像增广3. 读取数据集3.1 创建Dataset实例3.2 创建DataLoader实例 4. 微调预训练模型4.1 定义网络结构4.2 定义损失函数和评估函数 5. 定义训练…...
翼兴消防监控 – 大数据可视化HTML源码
概述 在当今数据驱动的时代,大数据可视化已成为各行各业不可或缺的工具。幽络源今天为大家带来一款基于ECharts的消防监控大屏HTML源码,帮助开发者快速搭建专业级数据展示界面。这款源码设计简洁大方,功能完善,适合各类监控场景使…...
搭建运行若依微服务版本ruoyi-cloud最新教程
搭建运行若依微服务版本ruoyi-cloud 一、环境准备 JDK > 1.8MySQL > 5.7Maven > 3.0Node > 12Redis > 3 二、后端 2.1数据库准备 在navicat上创建数据库ry-seata、ry-config、ry-cloud运行SQL文件ry_20250425.sql、ry_config_20250224.sql、ry_seata_2021012…...
火山引擎AI大模型
火山引擎的AI大模型(如**“云雀”大模型**)作为字节跳动旗下的核心技术产品,在性能、应用场景和技术生态上表现较为突出,尤其在字节自身业务(如抖音、TikTok等)的打磨下,具备较强的实用性和行业…...
isaacgym环境安装
1. 系统环境 操作系统:Ubuntu 18.04.6 LTSGPU:NVIDIA TITAN RTXDriver 版本: 510.108.03CUDA 版本:11.6 2. conda安装以及环境安装 略过(参考内容:https://github.com/unitreerobotics/unitree_rl_gym/blob/main/doc…...
使用Mathematica制作Lorenz吸引子的轨道追踪视频
Lorenz奇异吸引子是混沌理论中最早被发现和研究的吸引子之一,它由Edward Lorenz在1963年研究确定性非周期流时提出。Lorenz吸引子以其独特的"蝴蝶"形状而闻名,是混沌系统和非线性动力学的经典例子。 L NDSolveValue[{x[t] -3 (x[t] - y[t]),…...
基于matlab的D2D 功率控制仿真
基于MATLAB的D2D(Device-to-Device)功率控制仿真示例,包含系统建模、功率控制算法实现和性能分析。该仿真以蜂窝网络为背景,重点关注D2D用户间的干扰管理和功率优化。 1. 系统模型与参数设置 clc; clear; close all;%% 参数配置…...
将three.js场景保存成图片
使用html2canvas或者canvas.toDataURL,直接转会发现场景是空白的 解决方案:在转图片之前先渲染一下场景,就不会导致因为渲染问题的闪白了 1. 获取dom let canvas: any renderer.domElement;2. 转图片并下载 renderer.render(scene,camera…...
Java注解详解:从入门到实战应用篇
1. 引言 Java注解(Annotation)是JDK 5.0引入的一种元数据机制,用于为代码提供附加信息。它广泛应用于框架开发、代码生成、编译检查等领域。本文将从基础到实战,全面解析Java注解的核心概念和使用场景。 2. 注解基础概念 2.1 什…...
在哪一个终端下运行有影响吗?pip install pillow
在哪一个终端下运行有影响吗?pip install pillow -i https://pypi.tuna.tsinghua.edu.cn/simple --trusted-host pypi.tuna.tsinghua.edu.cn,需要切换到主目录吗? 1. 是否需要切换目录? 不需要切换目录 pip install 安装的包会存放…...
Java与C/C++跨平台互操作深度解析:Project Panama技术实战
简介 Project Panama是Java语言历史上最重要的互操作性增强项目之一,它通过Foreign Function & Memory API彻底改变了Java与本地代码交互的方式。相比传统的JNI方法,Panama提供了更安全、更高效、更易用的API,使得Java程序能够直接调用C/C++函数并操作外部内存,而无需…...
亲缘半相合供者
https://health.baidu.com/m/detail/ar_3602431741690417249 骨髓移植:亲缘半相合供者的经历分享 从去年八月开始,我妈妈被诊断为急性髓系白血病M4。经过一系列的准备和手术,昨天上午我终于完成了骨髓采集。现在,我的干细胞已经…...
《解锁数字藏品交易系统的底层密码:区块链架构抉择》
区块链,凭借其去中心化、不可篡改、可追溯等特性,成为数字藏品交易系统的核心支撑技术。不同的区块链底层架构,就像各具特色的建筑材料与蓝图,拥有独特的性能、特点以及应用场景。在搭建数字藏品交易系统时,必须深入剖…...
STM32F407VET6的HAL库使用CRC校验的思路
CRC校验在数据传输快,且量大的时候使用。 步骤实现: CubeMX配置 c // 在CubeMX中启用CRC模块 // AHB总线时钟自动启用 HAL库代码 c // 初始化(main函数中) CRC_HandleTypeDef hcrc; hcrc.Instance CRC; hcrc.Init.Default…...
TDuckX 2.6 正式发布|API 能力开放,核心表单逻辑重构,多项实用功能上线。
大家好,TDuckX 2.6 已正式发布。 本次更新以可集成性提升、数据处理能力增强和交互体验优化为核心,新增了包括 新增OpenAPI 模块、表单数据批量修改、字段导出分列 等多个面向开发者和实际业务落地场景的功能。 我们也重构了部分底层逻辑模块ÿ…...
智慧换热站全流程可视化管理
通过图扑 HT 实现换热站全方位数字孪生。对换热机组、管道阀门等设备精细建模,集成温度、流量、能耗等运行数据。在可视化界面中,设备状态实时呈现,异常即时预警。助力运维人员精准管理,优化调控策略,提升换热站运行效…...
框架的源码理解——V3中的ref和reactive
最近在研究各个框架的源码,从源码角度去理解 vue3 的 reactive 和 ref API,记录下研究的成果 reactive 首先,reactive() 的参数必须是一个对象,返回值是一个 Proxy 对象,具有响应性。如果参数不是对象类型࿰…...
一台入网的电脑有6要素, 机器名,mac,ip,俺码,网关,dns,分别有什么作用
一台入网的电脑需要配置的 六大网络要素(机器名、MAC地址、IP地址、子网掩码、网关、DNS)各自承担不同的关键作用,共同确保设备能正确通信和访问网络资源。以下是它们的详细功能解析: 1. 机器名(主机名) 作…...
LED点阵屏模块
目录 1.LED点阵屏介绍 2.显示原理 3.74HC595 4.C51的sfr、sbit 5.LED点阵屏显示图形代码 第一步: 第二步: 第三步: 第四步: 第五步: 第六步: 最终代码: 模块: main.c …...
服务器性能参数分析基础:磁盘-CPU-内存
在Linux系统中,"挂载"(Mount)是指将物理存储设备(如磁盘分区)或逻辑存储卷(如LVM、网络存储)关联到文件系统目录树的特定路径节点(即挂载点),使得该…...
MoonBit 新特性:Virtual Package 虚拟包机制
Moonbit 最近新增了一项特性:virtual package。通过将一个 package 声明为虚拟包,定义好一套接口(通过 .mbti 文件声明),用户可选择具体使用哪一份实现,如不指定则使用该虚拟包的默认实现。通过这项特性&am…...
[特殊字符][特殊字符]知识库PHP版 | ChatMoneyAI宝塔面板Docker多部署
官方文档💰📚知识库PHP版 | ChatMoneyAI docker-compose2.yml 修改文件名 ports:- "181:80" #【180】为Nginx挂载主机的端口 fastcgi_pass php1:9000; #名称修改php1 container_name: nginx1 #Nginx容器名修改 container_name: php1 #P…...
来一个复古的技术FTP
背景 10年前的老代码,需要升级springboot框架,在升级过程中,测试业务流程里,有FTP的下载业务,不管测试环境如何测试,都没有成功,最后只能自己搭建一个FTP服务器,写一个ftp-demo来测试…...
ShardingSphere:查询报错:Actual table `数据源名称.表名` is not in table rule configuration
目录 简介异常信息排查原因解决 简介 1、使用ShardingSphere框架,版本为5.2.1 <dependency><groupId>org.apache.shardingsphere</groupId><artifactId>shardingsphere-jdbc-core</artifactId><version>5.2.1</version>…...
内部检测实验室数字化转型新路径 质检LIMS系统如何实现合规、效率、资质三重突破?
在高质量发展成为主旋律的今天,内部检测实验室作为企业质量管控的 “心脏”,正面临着合规标准升级、检测任务激增、设备管理复杂等多重挑战。传统管理模式下,数据追溯难、人员效率低、资质评审周期长等问题,成为制约实验室发展的核…...
PyTorch 的 F.scaled_dot_product_attention 返回Nan
“为什么 PyTorch 的 scaled_dot_product_attention 会输出 NaN?如何正确构造 Attention Mask” 引言:看似正常的 mask,为什么会引发 NaN? 在使用 F.scaled_dot_product_attention 构建跨模态或多源注意力时,我们常通…...
MySQL的触发器
本章了解一下即可,并不是很难,大家加油!!! 触发器实际上是多表关联的一个操作,无需调用,是一个自动的过程,当对数据库表中的数据执行DML操作时自动触发这个SQL片段的执行࿰…...
高可用消息队列实战:AWS SQS 在分布式系统中的核心解决方案
引言:消息队列的“不可替代性” 在微服务架构和分布式系统盛行的今天,消息队列(Message Queue) 已成为解决系统解耦、流量削峰、异步处理等难题的核心组件。然而,传统的自建消息队列(如RabbitMQ、Kafka&am…...
内存泄漏系列专题分析之十六:高通相机CamX内存泄漏内存占用分析--chi-cdk部分ION内存拆解方法
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:内存泄漏系列专题分析之十五:高通相机CamX架构chi-cdk部分ION内存管理机制CHI ImageBuffer原理 这一篇我们开始讲: 内存泄漏系列专题分析之十六:高通相机CamX内存泄漏&内存占用分析--chi-cdk部分…...
HANA数据库死锁
死锁是两个或多个事务相互交叉锁定的情况,因此任何事务都无法继续进行。 通常死锁是由应用程序设计缺陷引起的,但在主键约束的上下文中也可能存在更多的技术死锁(这种情况请参考 SAP note 2429521)。 当 HANA 数据库出现死锁时&am…...
Unity雷火UX工具插件中的本地化功能(Unity项目中文字图片多语言功能)
一、插件下载地址如下: 雷火UX工具插件下载 二、本地化功能,也就是多语言功能官方文档地址如下: 雷火UX工具本地化功能官方文档 三、UX Image组件需要注意的事项,也就是官方文档没有提到的地方。 1、UX Image组件所引用的Source Image资...
公路水运安全员B证主要考核内容有哪些
公路水运安全员B证(交安B证)是交通运输行业施工企业主要负责人和项目负责人必备的安全生产考核证书,主要考核以下内容: 一、安全生产法律法规(30%) 国家法律法规:重点考核《安全生产法》《建设…...
电缆故障常见故障及应对方法
一、常见故障类型及原因 1. 机械损伤 原因:安装时的过度牵引或弯曲、外力破坏(如施工挖损、车辆振动)、自然因素(如土地沉降导致电缆拉伸)等。 表现:绝缘层破损、金属护套裂损,可能发展为短…...
Rust 数据结构:String
Rust 数据结构:String Rust 数据结构:String什么是字符串?创建新字符串更新字符串将 push_str 和 push 附加到 String 对象后使用 运算符和 format! 宏 索引到字符串字符串在内存中的表示字节、标量值和字形簇 分割字符串遍历字符串的方法 R…...
算法基础 -- 小根堆构建的两种方式:上浮法与下沉法
小根堆构建的两种方式:上浮法与下沉法 在构建小根堆(Min-Heap)时,通常有两种常见的构建方式: 上浮建堆(逐个插入,上浮调整)下沉建堆(Heapify 自底向上,下沉…...
Sprnig MVC 如何统一异常处理 (Exception Handling)?
主要有以下几种方式来实现统一异常处理,其中 ControllerAdvice (或 RestControllerAdvice) 结合 ExceptionHandler 是最常用的方式。 1. ExceptionHandler 注解 作用: 用于标记一个方法,该方法将处理在同一个 Controller 类中抛出的特定类型…...
03、基础入门-SpringBoot的大时代背景
03、基础入门-SpringBoot的大时代背景 # Spring Boot的大时代背景 Spring Boot的出现和发展,与以下时代背景密切相关: ## 1. 微服务架构的兴起 ### 背景 随着互联网应用的复杂度增加,传统的单体架构在扩展性、维护性和团队协作方面遇到瓶…...
【51单片机中断】
目录 配置流程 1.在IE寄存器中开启总中断通道和需要的某中断通道 2.在TCON寄存器开启所用中断的触发方式 3.使用中断函数完成中断 4.若需要中断嵌套则在IP寄存器中配置 5.若需要使用串口的中断,则配置SCON寄存器 6.代码示例 配置流程 1.在IE寄存器中开启总中…...
英飞凌tle9954 GPIO
9 通用输入输出(GPIO) 9.1 功能概述 通用输入 / 输出(GPIO)由输入 / 输出驱动级和端口控制逻辑组成。GPIO 具备以下功能: 输入 / 输出端口功能(PBx) 输出状态可编程,输入状态可读。输出驱动器可编程为推挽、开漏和三态模式。输出驱动器的驱动强度和转换速率(压摆率)可…...
Linux操作系统--进程间通信(system V共享内存)
目录 1.system V共享内存 2.共享内存数据结构 3.共享内存函数 4.实例代码: 1.system V共享内存 共享内存区是最快的IPC(进程间通信)形式。一旦这样的内存映射到共享它的进程地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再…...
力扣HOT100之二叉树:102. 二叉树的层序遍历
这道题太简单了,相当于基础的模板题,但凡涉及到层序遍历一定会用到队列来实现,其他的倒没啥好说的,用两层while循环来层序遍历,外层while循环用于控制访问二叉树的每一层,而内层while循环则负责收割每一层的…...
分布式锁: Redisson 实现分布式锁的原理与技术细节
在分布式系统中,分布式锁是协调多个节点对共享资源访问的核心机制之一。Redis 作为高性能内存数据库,常被用于实现分布式锁,而 Redisson 是 Java 生态中最成熟、功能最丰富的 Redis 客户端之一,其内置的分布式锁实现被广泛应用于生…...