Java并发08:并发编程的三大核心问题 - 原子性、可见性、有序性

引言:为什么synchronized能解决所有问题? // volatile只解决部分问题 private volatile int count = 0; count++; // 仍然不安全! // synchronized解决所有问题 private int count = 0; public synchronized void increment() { count++; // 安全 } 为什么? 关键在于理解并发编程的三大核心问题: 原子性(Atomicity) 可见性(Visibility) 有序性(Ordering) 一、原子性(Atomicity) 1.1 什么是原子性? 定义:一个操作或多个操作,要么全部执行且执行过程不被打断,要么都不执行。 int a = 10; // ✅ 原子操作 a++; // ❌ 非原子操作(3条指令) 1.2 哪些操作是原子的? 原子操作: // ✅ 基本类型的读写(long/double除外) int a = 1; boolean b = true; // ✅ 引用类型的读写 Object obj = new Object(); // ✅ volatile变量的读写 volatile int count = 0; count = 1; // 原子的 非原子操作: // ❌ long/double(64位,需要两次操作) long value = 123L; // 非原子的(未加volatile) // ❌ 复合操作 count++; // read-modify-write array[index]++; // 非原子 // ❌ check-then-act if (map.get(key) == null) { map.put(key, value); // 竞态条件 } 1.3 如何保证原子性? 方式1:synchronized public synchronized void increment() { count++; // 整个方法是原子的 } 方式2:Lock private Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); } } 方式3:原子类 private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); // 原子操作 } 二、可见性(Visibility) 2.1 什么是可见性? 定义:当一个线程修改了共享变量,其他线程能够立即看到这个修改。 ...

2025-11-20 · maneng

Java并发11:happens-before原则 - JMM的核心规则

引言:如何判断程序是否线程安全? 看这段代码,能否确定线程安全? public class DataRace { private int data = 0; private boolean ready = false; // 线程1 public void writer() { data = 42; // 1 ready = true; // 2 } // 线程2 public void reader() { if (ready) { // 3 System.out.println(data); // 4 一定输出42吗? } } } 三个问题: 线程2能看到ready = true吗?(可见性) 如果看到ready = true,能保证看到data = 42吗?(有序性) 如何用形式化的规则判断? 传统方法是分析各种可能的执行顺序,但这太复杂了! JMM的解决方案:happens-before原则 happens-before是JMM的核心规则,它定义了: 什么时候一个操作的结果对另一个操作可见 什么时候两个操作不能重排序 如何建立正确的并发语义 掌握happens-before原则,就能快速判断程序是否线程安全。 一、happens-before的定义 1.1 形式化定义 happens-before关系(简写为 hb): 如果操作A happens-before 操作B,记作 A hb B,则: 可见性保证:A的结果对B可见 有序性保证:A在B之前执行(从程序语义角度) 注意: ...

2025-11-20 · maneng

Java并发10:Java内存模型(JMM) - 抽象的内存模型

引言:从硬件到Java的抽象 在上一篇文章中,我们学习了CPU缓存、MESI协议、False Sharing等硬件层面的并发机制。但问题来了: 作为Java开发者,我们需要关心这些底层细节吗? 看这个例子: public class VisibilityProblem { private boolean flag = false; private int data = 0; // 线程1 public void writer() { data = 42; // 1 flag = true; // 2 } // 线程2 public void reader() { if (flag) { // 3 int result = data; // 4 System.out.println(result); // 可能输出0! } } } 三个疑问: 为什么线程2可能看不到flag = true?(可见性问题) 为什么即使看到flag = true,也可能读到data = 0?(有序性问题) 这个行为在不同CPU上是否一致?(平台差异) Java的解决方案:Java内存模型(JMM) JMM就像一份**“合同”**: 对JVM实现者:规定必须遵守的行为规范 对Java程序员:提供统一的并发语义保证 屏蔽平台差异:无论x86、ARM还是RISC-V,行为一致 本篇文章,我们将深入理解JMM的设计理念和工作机制。 一、为什么需要Java内存模型? 1.1 硬件层面的挑战 不同CPU架构的内存模型差异巨大: CPU架构 内存模型 Store-Load重排序 说明 x86/x64 TSO (Total Store Order) 否 较强的内存模型,大部分情况有序 ARM Weak 是 弱内存模型,重排序激进 RISC-V RVWMO 是 类似ARM,弱内存模型 PowerPC Weak 是 弱内存模型 示例:同样的Java代码,在x86上可能正常,在ARM上可能出错! ...

2025-11-20 · maneng

Java并发第一性原理:为什么并发如此困难?

引子:一个秒杀系统的困局 假设你正在开发一个电商秒杀系统,库存100件商品,瞬间涌入1000个并发请求。看似简单的需求,却隐藏着并发编程最本质的困难。 场景A:单线程实现(简单但性能差) /** * 单线程秒杀服务 * 优势:简单、可预测、无并发问题 * 劣势:性能差,无法处理高并发 */ public class SingleThreadSeckillService { private int stock = 100; // 库存 public synchronized boolean seckill(String userId) { // 检查库存 if (stock <= 0) { System.out.println("库存不足,秒杀失败"); return false; } // 模拟业务处理(数据库操作、支付调用等) try { Thread.sleep(10); // 10ms的业务处理时间 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } // 扣减库存 stock--; System.out.println("用户 " + userId + " 秒杀成功,剩余库存:" + stock); return true; } } // 性能测试 public class SingleThreadTest { public static void main(String[] args) { SingleThreadSeckillService service = new SingleThreadSeckillService(); long start = System.currentTimeMillis(); // 1000个用户顺序秒杀 for (int i = 0; i < 1000; i++) { service.seckill("User-" + i); } long end = System.currentTimeMillis(); System.out.println("总耗时:" + (end - start) + "ms"); } } /* 执行结果: 总耗时:10,000ms(10秒) 分析:每个请求10ms,1000个请求串行执行,总计10秒 问题:在真实秒杀场景中,10秒是不可接受的响应时间 */ 性能瓶颈分析: ...

2025-11-03 · maneng

如约数科科技工作室

浙ICP备2025203501号

👀 本站总访问量 ...| 👤 访客数 ...| 📅 今日访问 ...