深入理解Java内存模型与volatile关键字:从理论到实践

news/2025/10/16 8:42:21/文章来源:https://www.cnblogs.com/sun-10387834/p/19144715

1. 引言:为什么需要理解内存模型?

在多核处理器成为主流的今天,并发编程已成为每个Java程序员的必备技能。然而,编写正确的并发程序远比单线程程序复杂,主要原因在于我们需要处理两个核心问题:

  • 线程之间如何通信?
  • 线程之间如何同步?

Java内存模型(JMM)正是为了解决这些问题而设计的抽象概念。理解JMM不仅有助于编写正确的并发程序,还能帮助我们更好地利用现代硬件的性能优势。

2. Java内存模型的基础概念

2.1 并发编程的两个关键问题

通信机制:共享内存 vs 消息传递

/*** 共享内存模型示例* Java采用共享内存模型,线程通过读写共享变量进行隐式通信*/
public class SharedMemoryExample {private int sharedData = 0;  // 共享变量// 线程A通过写入共享变量与线程B通信public void threadA() {sharedData = 42;  // 隐式通信:通过修改共享变量}// 线程B通过读取共享变量接收通信public void threadB() {if (sharedData == 42) {System.out.println("收到线程A的消息");}}
}

现实比喻:把共享内存想象成公司的公告板

  • 员工A在公告板上贴通知(写共享变量)
  • 员工B查看公告板获取信息(读共享变量)
  • 不需要直接对话,通过公告板间接通信

同步机制:显式 vs 隐式

/*** Java需要显式同步* 程序员必须明确指定哪些代码需要互斥执行*/
public class ExplicitSynchronization {private final Object lock = new Object();private int counter = 0;public void increment() {synchronized(lock) {  // 显式同步counter++;        // 临界区代码}}
}

2.2 JMM的抽象结构

三层存储架构

┌─────────────┐    ┌─────────────┐
│   线程A      │    │   线程B      │
│             │    │             │
│ 本地内存A    │    │ 本地内存B    │
│ ┌─────────┐ │    │ ┌─────────┐ │
│ │共享变量 │ │    │ │共享变量 │ │
│ │ 副本    │ │    │ │ 副本    │ │
│ └─────────┘ │    │ └─────────┘ │
└──────┬──────┘    └──────┬──────┘│                  │└──────────────────┘│JMM控制交互│┌──────┴──────┐│  主内存      ││ ┌─────────┐ ││ │共享变量 │ ││ └─────────┘ │└─────────────┘

关键理解

  • 主内存:存储所有共享变量的"中央仓库"
  • 本地内存:每个线程的"工作缓存",包含共享变量的副本
  • JMM:控制主内存与本地内存之间交互的"交通管理系统"

线程通信的详细过程

public class ThreadCommunication {private int sharedVariable = 0;public void demonstrateCommunication() {// 初始状态:主内存和所有本地内存中 sharedVariable = 0// 线程1执行:sharedVariable = 100;  // 1. 修改本地内存中的副本// 2. 在某个时刻刷新到主内存// 线程2执行:int value = sharedVariable;  // 3. 从主内存加载最新值// 4. 存入本地内存副本}
}

3. 重排序:看不见的性能优化

3.1 什么是重排序?

重排序是编译器和处理器为了优化程序性能而对指令序列进行重新排序的一种手段。

public class ReorderingDemo {private int a = 0, b = 0;public void noReorder() {// 程序员看到的顺序a = 1;      // 操作1b = 2;      // 操作2int c = a + b; // 操作3}// 实际可能执行的顺序public void actualExecution() {b = 2;      // 操作2先执行a = 1;      // 操作1后执行(重排序!)int c = a + b; // 操作3:结果仍然是3}
}

现实比喻:聪明的厨师优化做菜流程

  • 新手厨师:严格按菜谱顺序,先烧水→再切菜→最后煮面(耗时8分钟)
  • 资深厨师:先烧水→在等水开时切菜→水开了煮面(耗时5分钟,结果相同)

3.2 数据依赖性:重排序的底线

三种数据依赖类型

public class DataDependency {// 1. 写后读 (Write After Read)public void writeAfterRead() {a = 1;      // 写操作b = a;      // 读操作 ← 不能重排序!}// 2. 写后写 (Write After Write)public void writeAfterWrite() {a = 1;      // 第一次写a = 2;      // 第二次写 ← 不能重排序!}// 3. 读后写 (Read After Write)public void readAfterWrite() {b = a;      // 读操作a = 1;      // 写操作 ← 不能重排序!}
}

数据依赖的现实意义

public class CookingDependencies {public void makeSandwich() {// 有依赖的操作(不能重排序):bread = toast();              // 必须先烤面包sandwich = putFilling(bread); // 才能放馅料// 无依赖的操作(可以重排序):prepareLettuce();  // 准备生菜prepareTomato();   // 准备番茄 ← 顺序可以交换}
}

3.3 as-if-serial语义:单线程的幻觉

as-if-serial语义:不管怎么重排序,单线程程序的执行结果不能被改变。

public class AsIfSerialExample {public double calculateArea() {double pi = 3.14;       // 操作Adouble r = 1.0;         // 操作Bdouble area = pi * r * r; // 操作Creturn area; // 总是返回3.14,无论A和B的执行顺序}
}

数据依赖分析

  • A → C(pi用于计算area)
  • B → C(r用于计算area)
  • A ↔ B(A和B没有依赖,可以重排序)

3.4 重排序对多线程的影响

问题代码示例

public class ReorderingProblem {int a = 0;boolean flag = false;// 线程A执行public void writer() {a = 1;          // 操作1:设置数据flag = true;    // 操作2:发布标志}// 线程B执行public void reader() {if (flag) {         // 操作3:检查标志int i = a * a;  // 操作4:使用数据System.out.println("结果: " + i);}}
}

两种危险的重排序

情况1:操作1和2重排序

// 期望顺序:a=1 → flag=true
// 重排序后:flag=true → a=1
// 结果:线程B可能看到flag=true但a=0

情况2:操作3和4重排序(猜测执行)

// 期望顺序:检查flag → 计算a*a
// 重排序后:提前计算a*a → 检查flag
// 结果:可能使用过期的a值进行计算

4. 顺序一致性:理想的内存模型

4.1 什么是顺序一致性?

顺序一致性是一个理论参考模型,为程序员提供极强的内存可见性保证。

public class SequentialConsistency {// 两大特性:// 1. 线程内顺序不变public void perfectOrder() {step1();  // 严格按顺序执行step2();  // 严格按顺序执行step3();  // 严格按顺序执行}// 2. 全局统一视图public void globalView() {// 所有线程看到相同的操作执行顺序// 操作立即对所有线程可见}
}

现实比喻:完美的电影放映系统

  • 每个场景严格按剧本顺序播放
  • 所有观众看到完全相同的画面
  • 画面切换瞬间同步到所有观众

4.2 顺序一致性的工作机制

全局内存开关比喻

[线程1] [线程2] [线程3] ... [线程N]↓    ↓    ↓         ↓┌─────────────────────────┐│      全局内存开关        │ ← 像老式电话总机└─────────────────────────┘↓[全局内存]工作方式:
1. 开关每次只连接一个线程到内存
2. 该线程执行一个完整操作
3. 然后开关切换到下一个线程
4. 所有操作串行执行,全局可见

4.3 JMM vs 顺序一致性

public class JMMvsSequential {// 顺序一致性模型(理想):class IdealWorld {// - 单线程严格按程序顺序执行// - 所有线程看到相同的操作顺序// - 所有操作立即全局可见// - 所有操作原子执行}// JMM现实模型:class RealWorld {// - 单线程内可能重排序(优化)// - 不同线程可能看到不同的操作顺序// - 写操作可能延迟可见(本地缓存)// - long/double可能非原子操作}
}

5. volatile的内存语义深度解析

5.1 volatile的基本特性

volatile与锁的等价性

public class VolatileEquivalence {// 使用volatile的版本class VolatileVersion {volatile long counter = 0;public void set(long value) {counter = value;  // 单个volatile写}public long get() {return counter;   // 单个volatile读}}// 使用锁的等价版本class LockVersion {long counter = 0;final Object lock = new Object();public void set(long value) {synchronized(lock) {counter = value;}}public long get() {synchronized(lock) {return counter;}}}
}

关键理解

  • 单个volatile变量的读写 ≈ 用同一个锁同步的普通变量读写
  • volatile++ ≠ 原子操作,需要额外同步

5.2 volatile的happens-before关系

经典的volatile通信模式

public class VolatileCommunication {private int data = 0;private volatile boolean ready = false; // volatile信号标志// 生产者线程public void producer() {data = 42;          // 1. 准备数据ready = true;       // 2. 发出信号(volatile写)}// 消费者线程public void consumer() {if (ready) {        // 3. 检查信号(volatile读)int result = data; // 4. 使用数据System.out.println("结果: " + result); // 保证输出42}}
}

happens-before关系链

data = 42  →  ready = true  →  if(ready)  →  result = data↑              ↑              ↑              ↑
步骤1             步骤2           步骤3           步骤4↓              ↓              ↓              ↓
程序顺序规则   volatile规则   程序顺序规则   传递性规则

关系推导

  1. 1 happens-before 2(程序顺序规则)
  2. 2 happens-before 3(volatile规则:写先于读)
  3. 3 happens-before 4(程序顺序规则)
  4. 因此:1 happens-before 4(传递性)

结果:如果消费者看到ready=true,那么它一定能看到data=42

5.3 volatile的内存语义

volatile写:发送消息

public class MessageSending {// volatile写就像发送广播消息:public void sendMessage() {prepareData();          // 准备消息内容messageReady = true;    // volatile写:广播发送// 效果:所有准备的数据连带消息一起"发出"}
}

volatile写的内存效果

  • 刷新线程本地内存中的所有共享变量到主内存
  • 确保写操作之前的所有修改都对其他线程可见

volatile读:接收消息

public class MessageReceiving {// volatile读就像打开收件箱:public void receiveMessage() {if (messageReady) {     // volatile读:检查新消息// 自动效果:清空本地缓存,重新加载所有数据processData();      // 处理接收到的数据}}
}

volatile读的内存效果

  • 使线程的本地内存无效
  • 强制从主内存重新加载所有共享变量

5.4 volatile内存语义的实现:内存屏障

内存屏障的作用

public class MemoryBarrierDemo {private int x, y;private volatile boolean flag;public void writer() {x = 1;                  // 普通写y = 2;                  // 普通写// StoreStore屏障:确保x=1, y=2先完成flag = true;            // volatile写// StoreLoad屏障:确保flag=true立即可见}public void reader() {// LoadLoad屏障:确保之前的读取完成if (flag) {             // volatile读// LoadStore屏障:确保后续写入基于正确状态int sum = x + y;    // 保证看到x=1, y=2}}
}

四种内存屏障的详细解释

屏障类型 作用 现实比喻 插入位置
StoreStore 确保前面的写完成再执行后面的写 先炒完菜再装盘 volatile写之前
StoreLoad 确保前面的写完成再执行后面的读 先生产完产品再质量检查 volatile写之后
LoadLoad 确保前面的读完成再执行后面的读 先读完第一章再读第二章 volatile读之后
LoadStore 确保前面的读完成再执行后面的写 先诊断病情再开药方 volatile读之后

实际的屏障插入策略

public class ActualBarrierInsertion {int a;volatile int v1 = 1;volatile int v2 = 2;void readAndWrite() {int i = v1;     // volatile读// LoadLoad屏障(可能被省略)int j = v2;     // volatile读// LoadStore屏障a = i + j;      // 普通写// StoreStore屏障v1 = i + 1;     // volatile写// StoreStore屏障v2 = j * 2;     // volatile写// StoreLoad屏障(必须保留)}
}

优化原理

  • 编译器根据具体上下文省略不必要的屏障
  • 但最后的StoreLoad屏障通常不能省略
  • 不同处理器平台有不同优化策略

5.5 volatile的使用场景和限制

适合使用volatile的场景

public class GoodVolatileUse {// 场景1:状态标志private volatile boolean shutdownRequested = false;public void shutdown() {shutdownRequested = true;}public void doWork() {while (!shutdownRequested) {// 正常工作}System.out.println("程序已停止");}// 场景2:一次性安全发布private volatile Resource resource;public Resource getResource() {if (resource == null) {synchronized(this) {if (resource == null) {Resource temp = new Resource();// volatile写确保对象完全构造后对其他线程可见resource = temp;}}}return resource;}
}

不适合使用volatile的场景

public class BadVolatileUse {// 错误:volatile不能保证复合操作的原子性private volatile int counter = 0;public void unsafeIncrement() {counter++;  // 这不是原子操作!// 实际包含:读 → 修改 → 写 三个步骤// 多线程环境下可能丢失更新}// 正确做法:使用AtomicInteger或锁private AtomicInteger safeCounter = new AtomicInteger(0);public void safeIncrement() {safeCounter.incrementAndGet(); // 原子操作}
}

6. 扩展知识:MESI协议 - 硬件层面的缓存一致性

6.1 MESI协议基础:图书馆管理系统

基础概念映射

现实世界比喻:大型企业图书馆系统
─────────────────────────────────────
技术概念        ↔   现实比喻
─────────────────────────────────────
CPU核心         ↔   不同部门的会议室
缓存            ↔   会议室里的白板
主内存          ↔   中央档案室
缓存行          ↔   白板上的一个主题区域
MESI状态        ↔   白板的使用权限状态
总线            ↔   公司内部广播系统
内存屏障        ↔   强制同步会议

四种状态的现实意义

public class MESIStateMetaphors {// Modified (M) - 已修改状态// 比喻:你在会议室白板上做了独家修改,还没同步到中央档案室// 特点:只有你有最新版本,别人看到的都是过时的// Exclusive (E) - 独占状态// 比喻:你借了档案室的资料,只有你的会议室有复印件// 特点:你是唯一持有者,可以随时修改// Shared (S) - 共享状态  // 比喻:多个会议室都有同一份资料的复印件// 特点:大家看到的内容都一样,但不能直接修改// Invalid (I) - 无效状态// 比喻:你会议室的资料复印件已过期作废// 特点:不能使用这份资料,需要重新获取
}

6.2 MESI协议完整状态转换的比喻场景

场景1:第一次获取资料(I → E)

技术场景:CPU第一次读取未缓存的数据

现实比喻

市场部会议室(初始状态I):
1. 需要"年度销售报告"资料
2. 检查白板:没有这份资料
3. 通过广播系统:"谁有年度销售报告?"
4. 其他部门:都没回应(说明没人有副本)
5. 中央档案室:提供原始报告
6. 市场部:将报告贴到白板上,标记"独占(E)"结果:只有市场部有这份报告,可以随时修改

场景2:共享阅读资料(E → S / I → S)

技术场景:第二个CPU读取同一数据

现实比喻

技术部会议室(初始状态I):
1. 也需要"年度销售报告"
2. 检查白板:没有这份资料
3. 广播:"我也需要年度销售报告!"市场部会议室(状态E)听到广播:
4. 回应:"我这里有,可以共享"
5. 将自己白板标记改为"共享(S)"
6. 提供复印件给技术部技术部会议室:
7. 获得复印件,标记"共享(S)"结果:两个部门都有相同报告,都不能单独修改

场景3:修改共享资料(S → M / S → I)

技术场景:CPU写入共享数据

现实比喻

市场部会议室(状态S):
1. 发现报告有错误需要修改
2. 但不能直接修改(因为是共享状态)
3. 广播:"我要修改报告,请销毁你们的复印件!"技术部会议室(状态S)听到广播:
4. 立即销毁复印件
5. 将白板标记改为"无效(I)"  
6. 回应:"已销毁"市场部会议室:
7. 收到所有确认后修改报告
8. 将标记改为"已修改(M)"结果:只有市场部有最新版本,技术部的副本已作废

场景4:读取已修改资料(M → S / I → S)

技术场景:其他CPU读取被修改的数据

现实比喻

技术部会议室(状态I):
1. 需要查看最新报告
2. 检查白板:标记为I(复印件已销毁)
3. 广播:"我需要最新的年度销售报告!"市场部会议室(状态M)听到广播:
4. 将修改后的报告复印一份送到中央档案室更新
5. 提供最新复印件给技术部
6. 将自己标记改为"共享(S)"技术部会议室:
7. 获得最新复印件,标记"共享(S)"结果:两个部门又都有相同的最新报告

6.3 MESI协议与volatile的关系

volatile如何利用MESI协议

public class VolatileWithMESI {private volatile boolean flag = false;private int data = 0;public void writer() {data = 42;// volatile写会触发:// 1. 将缓存行状态改为M(Modified)// 2. 发送无效化消息给其他CPU// 3. 等待所有确认// 4. 将数据刷新到主内存flag = true;}public void reader() {// volatile读会触发:// 1. 检查缓存行状态,如果是I(Invalid)// 2. 发送读请求到总线// 3. 从主内存或其他CPU获取最新数据if (flag) {// 由于MESI协议,这里保证看到data=42System.out.println(data);}}
}

MESI协议的消息类型比喻

public class MESIMessageMetaphors {// 读请求 (Read)// 比喻:"谁有XXX资料?借我看看"// 目的:获取资料的只读副本// 读无效化 (ReadInvalidate) // 比喻:"我要修改XXX资料,请把你们的复印件都给我/销毁"// 目的:获取独占修改权// 无效化 (Invalidate)// 比喻:"你们手里的XXX资料已过期,请立即销毁"// 目的:使其他副本失效// 写回 (WriteBack)// 比喻:"我把修改后的资料送回中央档案室更新"// 目的:将修改同步到主存储// 响应 (Response)// 比喻:"我这里有资料,给你复印件"// 目的:提供数据给请求者
}

6.4 MESI协议的硬件实现细节

CPU缓存结构

public class CPUCacheStructure {// 典型的L1缓存结构:class L1Cache {int size;           // 32KBint associativity;  // 8路组相联int lineSize;       // 64字节CacheLine[] lines;  // 缓存行数组// 每个缓存行包含:class CacheLine {byte[] data;        // 64字节数据int tag;           // 地址标签MESIState state;   // MESI状态boolean dirty;     // 脏位int lruCounter;    // LRU计数}}
}

总线工作机制比喻

想象单车道大桥:[处理器A] [处理器B] [处理器C] [处理器D]↓        ↓        ↓        ↓┌─────────────────────────────┐│        交通警察            │ ← 总线仲裁器└─────────────────────────────┘↓[单车道大桥] ← 总线↓[对岸城市] ← 内存规则:
1. 每次只允许一辆车过桥(总线事务)
2. 警察决定谁先过(总线仲裁)
3. 过桥期间其他车等待
4. 保证每辆车完整过桥(原子性)

6.5 MESI协议的性能优化技术

写缓冲区(Write Buffer)优化

public class WriteBufferOptimization {// 问题:CPU写操作需要等待总线响应,造成停顿// 解决方案:写缓冲器class WriteBuffer {Queue<WriteRequest> pendingWrites;public void queueWrite(WriteRequest req) {// 将写请求放入缓冲区pendingWrites.add(req);// CPU可以继续执行,不必等待}}// 但这会引入内存重排序!// load X; store Y; 可能被重排序为 store Y; load X;
}

失效队列(Invalidate Queue)优化

public class InvalidateQueue {// 问题:处理失效请求会阻塞CPU// 解决方案:失效队列class InvalidateQueue {Queue<InvalidateRequest> pendingInvalidates;public void queueInvalidate(InvalidateRequest req) {// 快速确认失效,将请求放入队列pendingInvalidates.add(req);sendAcknowledge(); // 立即发送确认}}// 风险:CPU可能短暂看到过期的数据!
}

6.6 MESI协议的现实意义

伪共享问题

public class FalseSharing {// 问题:两个不相关的变量在同一缓存行class Problem {// 这两个变量可能在同一个64字节缓存行volatile long variableA;  // CPU1频繁修改volatile long variableB;  // CPU2频繁修改}// 结果:// CPU1修改variableA → 缓存行状态M→S→M→S...// CPU2修改variableB → 缓存行状态M→S→M→S...// 大量不必要的缓存一致性流量!// 解决方案:缓存行对齐class Solution {volatile long variableA;long p1, p2, p3, p4, p5, p6, p7; // 填充到64字节volatile long variableB;}
}

MESI协议与Java内存模型的关系

public class MESIAndJMM {// MESI协议提供了硬件基础:// - 缓存一致性保证// - 内存操作的有序性基础// Java内存模型建立在MESI之上:// - 通过内存屏障控制MESI状态转换// - 利用MESI协议实现volatile语义// - 在MESI基础上定义更高层次的抽象// 关系总结:// MESI是"物理实现",JMM是"编程接口"// volatile是"高级指令",内存屏障是"底层控制"
}

7. 实战指南:正确使用volatile

7.1 volatile使用模式

模式1:状态标志

public class StatusFlagPattern {private volatile boolean running = true;public void start() {// 工作线程new Thread(() -> {while (running) {// 执行工作任务doWork();}System.out.println("线程正常退出");}).start();}public void stop() {running = false;  // 其他线程可以立即看到这个变化}
}

模式2:观察者模式

public class ObserverPattern {private volatile int temperature;private volatile int humidity;// 传感器线程(生产者)public void updateReadings(int temp, int hum) {// 不需要同步,因为volatile保证可见性temperature = temp;humidity = hum;}// 显示线程(消费者)public void display() {// 总是看到最新的一致数据System.out.printf("温度: %d, 湿度: %d%n", temperature, humidity);}
}

7.2 volatile与锁的选择指南

场景 推荐方案 原因
简单的状态标志 volatile boolean 轻量级,性能好
独立变量的可见性 volatile 避免锁的开销
计数器 AtomicInteger 需要原子性
复杂的数据结构 synchronized 需要互斥访问
复杂的不变式 synchronized 需要原子性保证

7.3 常见陷阱与解决方案

public class CommonMistakes {// 陷阱1:误以为volatile++是原子的private volatile int count = 0;public void wrongIncrement() {count++;  // ❌ 不是原子操作!}public void correctIncrement() {synchronized(this) {count++;  // ✅ 使用锁保证原子性}}// 陷阱2:多个volatile变量需要单独保护private volatile int x, y;public void wrongUpdate() {x = 1;  // ❌ 两个写操作之间可能被其他线程打断y = 2;}public void correctUpdate() {synchronized(this) {x = 1;  // ✅ 使用锁保护复合操作y = 2;}}
}

8. 总结与最佳实践

8.1 核心要点总结

  1. JMM抽象结构

    • 主内存是共享变量的中央存储
    • 每个线程有本地内存作为工作缓存
    • 线程通过主内存进行间接通信
  2. 重排序优化

    • 编译器和处理器为了性能会重排序指令
    • 数据依赖性操作不会被重排序
    • 单线程程序通过as-if-serial语义保证正确性
  3. 顺序一致性

    • 理想的内存模型,提供最强的保证
    • 实际JMM在正确同步时提供顺序一致性效果
  4. volatile关键字

    • 保证可见性和一定的有序性
    • 通过内存屏障实现内存语义
    • 适合状态标志、安全发布等场景
  5. MESI协议

    • 硬件层面的缓存一致性协议
    • 通过四种状态管理缓存行的权限
    • volatile的内存语义在MESI协议基础上实现

8.2 最佳实践建议

public class BestPractices {// ✅ 推荐做法:// 1. 使用volatile作为状态标志private volatile boolean initialized = false;// 2. 一次性安全发布private volatile Singleton instance;// 3. 独立观察的变量private volatile int currentTemperature;// 4. 注意缓存行对齐,避免伪共享private volatile long data1;private long padding1, padding2, padding3, padding4, padding5, padding6, padding7; // 填充private volatile long data2;// ❌ 避免做法:// 1. 不要用volatile做计数器// private volatile int counter;  // 错误!// 2. 不要依赖多个volatile变量的复合操作// private volatile int x, y;    // 需要额外同步// 3. 不要过度使用volatile,在需要时才使用
}

8.3 最终思考

理解Java内存模型、volatile关键字和底层的MESI协议是编写正确并发程序的基础。记住这些核心原则:

  1. 共享变量需要同步:未同步的共享变量访问可能导致不可预测的结果
  2. volatile提供轻量级同步:适合简单的可见性需求,但不保证原子性
  3. 正确同步的程序具有顺序一致性:这是JMM给程序员的强保证
  4. MESI协议是硬件基础:理解MESI有助于理解volatile和内存屏障的工作原理
  5. 理解底层原理有助于调试:当遇到奇怪的并发bug时,理解JMM、内存屏障和MESI协议会很有帮助

把整个系统想象成智能的交通管理系统

  • 共享变量 = 十字路口
  • volatile = 交通信号灯
  • = 交通警察
  • 内存屏障 = 交通规则
  • MESI协议 = 车辆之间的通信协调系统

通过正确使用这些工具,我们可以构建既正确又高效的并发程序!


希望这篇全面的指南能帮助你深入理解Java内存模型、volatile关键字和底层的MESI协议。在实际开发中,建议结合具体场景选择合适的同步机制,并在性能需求和代码复杂度之间找到平衡点。理解这些底层原理不仅有助于编写正确的代码,还能在遇到复杂并发问题时提供有力的调试思路。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

完整教程:【stm32】cube固件解析和放入工程(HAL_F4)

完整教程:【stm32】cube固件解析和放入工程(HAL_F4)pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas&q…

312、金缕衣

312、金缕衣312、金缕衣 唐●杜秋娘 劝君莫惜金缕衣,劝君惜取少年时。 花开堪折直须折,莫待无花空折枝。【现代诗意译】 我劝你啊 不要看重华丽衣裳 但是 一定要珍惜 青春年少美好时光花开宜折的时候 就要立即把它摘…

使用 Visual Studio 快速创建 NuGet 程序包并发布到 NuGet 官网

前言 在前面的 .NET EF Core 快速入门实战教程章节中我们创建了一个名为 EFCoreGenericRepository 的 .NET 9 通用仓储类库,今天我们来把这个 EF Core 通用仓储类库打包成 NuGet 程序包并发布到 NuGet 官网(https://…

反配容斥

反配容斥模拟赛考了这个 trick,感觉挺牛的。 直接放题。 题意 给定一个长度为 \(n\) 的序列 \(\{ a_i \}\),令全集 \(U = \{ 1,2,3,\cdots,n \}\),定义子集 \(S\) 的权值 \(g(S)=1+\oplus_{i\in S} a_i\)。 我们称集…

怎么激活win11?笔记本重装系统后怎么激活Windows?

我可以肯定99%的人并不完全了解Windows的几种激活方式,或者知其一,不知其二。 windows10 突然右下角出现激活windows? 光广告就能搜索出一大堆,必定广告是要收费的,说明市场是有的,但我这是分享的是免费使用教程…

AVG Clear:彻底卸载AVG产品的专业工具

AVG Clear是一款专业的AVG产品卸载工具,当传统卸载方式失效时,可自动扫描并彻底删除AVG相关的文件、注册表项和安装文件,确保系统完全清理。适用于Windows 10/11系统。AVG Clear 下载 立即在作者网站下载 作者: AV…

深入解析:安卓 WPS Office v18.21.0 国际版

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

现代 PHP8+ 实战特性介绍 Enums、Fibers 和 Attributes

现代 PHP8+ 实战特性介绍 Enums、Fibers 和 Attributes PHP 一直是 Web 开发领域使用最广泛的语言之一。这些年来,它的特性不断演进,每个版本都让语言变得更有表现力、更高效、对开发者更友好。如果你一直在关注最近…

用【WPF+Dlib68】实现 侧脸 眼镜虚拟佩戴 - 用平面图表现空间视觉 - 行人-

一个精致的眼镜3D模型,在模型网站中的售价几百人民币是非常常见的,它的制作成本以及制作周期,对于实际落地的项目来说是一个非常大的阻碍。于是我尝试使用 平面眼镜图 来表现 眼镜在佩戴时的空间感 ,尤其是 侧脸 时…

比 26ai 更震撼的,是 Oracle AI 向量搜索改写的生命答案

比 "26ai" 更震撼的,是 Oracle AI 向量搜索改写的生命答案2025-10-16 08:02 AlfredZhao 阅读(0) 评论(0) 收藏 举报在 Oracle AI World 上,“26ai” 的名字成为外界讨论的焦点,引发广泛好奇与热议。 …

科学背景如何赋能云计算业务战略

本文讲述了一位神经科学博士如何将科研背景应用于云计算业务发展,通过理解科研工作者需求,帮助企业客户将研究负载迁移至云端,加速从原始数据到研究成果的转化过程。科学背景如何赋能云计算业务战略 安德烈亚皮尔斯…

.netframework中自带的dll

.netframework中自带的dllSystem.Data.OracleClient.dll .netframework中自带的dll, "C:\Windows\Microsoft.NET\Framework64\v2.0.50727\System.Data.OracleClient.dll" ``

通过pypdfium2-team/ctypesgen 快速生成ctypes 代码

通过pypdfium2-team/ctypesgen 快速生成ctypes 代码以前说过ctypesgen 的作用,以下是一个简单试用 项目准备代码结构├── add.c ├── add.h ├── add.so ├── app.py ├── common.h ├── init_patch.py ├…

【GitHub每日速递 251016】23k star,Daytona:90ms内极速运行AI代码,安全弹性基础设施来袭!

原文: https://mp.weixin.qq.com/s/QkAYh9t3n41cADcQUi6FXw Daytona:90ms内极速运行AI代码,安全弹性基础设施来袭! 项目地址:https://github.com/daytonaio/daytona 主要语言:TypeScript stars: 23k仓库概述 Dayt…

用 【C# + Winform + Dlib68点】 实现静图眼镜虚拟佩戴 - 行人-

基于DlibDotNet,识别人脸68点关键点,通过眼镜标定功能,使虚拟眼镜能更自然地贴合人脸。采用.NET Framework 4.6.2开发,通过计算眼镜标定点与人眼位置的相对关系,实现眼镜对齐和缩放。关键步骤:人脸检测模型加载、…

图神经网络前沿技术与应用探索

本文深入探讨图神经网络在建模长距离依赖关系、提升计算效率以及新型因果模型方面的最新进展,涵盖算法优化、系统设计和硬件协同等多个技术层面,并介绍在知识图谱推理和多智能体系统等领域的创新应用。KDD 2023:图神…

MVCC、幻读、间隙锁与临键锁(三)

一、MVCC解决了什么问题? MVCC 解决了数据库高并发场景下的两大核心问题:读写阻塞:在传统的锁机制下,读操作可能会阻塞写操作,写操作也一定会阻塞读操作。当有大量读写操作并发时,数据库性能会急剧下降。事务隔离…

MVCC、幻读、间隙锁与临键锁

一、MVCC 解决了什么问题? 🌱 背景:并发读写冲突 当多个事务同时操作同一行时,最经典的冲突是:A 在读;B 在写;A 还没提交,B 改了数据;如何让 A 看到一致的结果?MVCC(Multi-Version Concurrency Control,多…

MVCC、幻读、间隙锁与临键锁(二)

1. MVCC 解决了什么问题? MVCC(多版本并发控制)是 MySQL InnoDB 存储引擎实现并发访问的核心机制,主要解决了读写冲突问题:在传统锁机制中,读操作需要加共享锁,写操作需要加排他锁,会导致 “读阻塞写、写阻塞读…