倩女幽魂乐嗨嗨手游
1.87 GB · 2025-11-15
volatile 与 synchronized 的语义差异是什么?约束(Constraints)
i++ 不是不可分割 → 原子性问题。成本模型(Cost Model)
**volatile**:插入内存屏障、刷新/失效缓存行;相比加锁开销低,但不提供互斥。最小原语(Primitives)
LoadLoad/LoadStore/StoreLoad/StoreStore 约束读写顺序。volatile、synchronized、CAS(Atomic*/VarHandle)、final 字段的发布语义。可验证(Check)
volatile/锁/CAS 修复并验证消失。long/double 在现代 JDK 已保证单次读写的原子性(64 位),但复合操作不原子。记忆法:HB = “可见 + 不乱序”的契约。volatile/锁/线程启动终止等,都是建立 HB 的方法。
**volatile**** 变量规则**:对 volatile 的写 HB 之后对同一变量的读。Thread.start() HB 线程内的第一个动作。Thread.join() 的返回。interrupt() 的调用 HB 被中断线程检测到中断(isInterrupted()/InterruptedException)。finalize() 开始(几乎过时,仅了解)。加分:**final**** 字段语义**
final 字段写入主内存,且引用安全发布后,其他线程读取到的 final 字段值不可见被重写(除非反射/Unsafe 破坏),用于构建不可变对象。// 演示用途,可能需要多次运行才触发
public class ReorderingDemo {
static int x, y, a, b;
public static void main(String[] args) throws Exception {
int count = 0;
while (true) {
count++;
x = y = a = b = 0;
Thread t1 = new Thread(() -> { a = 1; x = b; });
Thread t2 = new Thread(() -> { b = 1; y = a; });
t1.start(); t2.start();
t1.join(); t2.join();
if (x == 0 && y == 0) {
System.out.println("Observed (0,0) at iteration: " + count);
break;
}
}
}
}
原因:无 HB 约束时,编译器/CPU 允许将写/读乱序,导致两个线程都在对方写之前读到初始值 0。
volatile 建立写→读 HBpublic class VolatileFix {
static volatile int a, b;
static int x, y;
public static void main(String[] args) throws Exception {
for (int i = 0; i < 1_000_00; i++) {
x = y = 0; a = b = 0;
Thread t1 = new Thread(() -> { a = 1; x = b; });
Thread t2 = new Thread(() -> { b = 1; y = a; });
t1.start(); t2.start();
t1.join(); t2.join();
if (x == 0 && y == 0) throw new AssertionError("HB violated");
}
System.out.println("No (0,0) with volatile.");
}
}
synchronized(互斥 + 可见性)public class SyncFix {
static int a, b, x, y;
static final Object lock = new Object();
public static void main(String[] args) throws Exception {
for (int i = 0; i < 100_000; i++) {
x = y = 0; a = b = 0;
Thread t1 = new Thread(() -> { synchronized (lock) { a = 1; x = b; }});
Thread t2 = new Thread(() -> { synchronized (lock) { b = 1; y = a; }});
t1.start(); t2.start();
t1.join(); t2.join();
if (x == 0 && y == 0) throw new AssertionError();
}
System.out.println("No (0,0) with synchronized.");
}
}
public class SingletonBroken {
private static SingletonBroken INSTANCE; // 非 volatile
private final byte[] data = new byte[1024];
private SingletonBroken() {}
public static SingletonBroken getInstance() {
if (INSTANCE == null) { // 1. 读
synchronized (SingletonBroken.class) {
if (INSTANCE == null) { // 2. 再次读
INSTANCE = new SingletonBroken(); // 3. 分配→构造→赋值(可能被重排)
}
}
}
return INSTANCE; // 4. 可能读到“未完全构造”的对象
}
}
问题:赋值与构造可能被重排(先把引用写入,再执行构造),其他线程读到非空引用但对象未完全初始化。
volatile 阻止重排 + 可见性)public class Singleton {
private static volatile Singleton INSTANCE; // 关键:volatile
private final byte[] data = new byte[1024];
private Singleton() {}
public static Singleton getInstance() {
if (INSTANCE == null) {
synchronized (Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton(); // 构造完成的发布对其他线程可见
}
}
}
return INSTANCE;
}
}
更进一步:若能容忍类初始化时创建实例,静态初始化或枚举实现更简单且天生安全:
// 静态内部类方式
public class HolderSingleton {
private HolderSingleton() {}
private static class Holder { static final HolderSingleton I = new HolderSingleton(); }
public static HolderSingleton getInstance() { return Holder.I; }
}
// 枚举
public enum EnumSingleton { INSTANCE; }
volatile 能与不能能:
不能:
count++ 仍然竞争)。LongAdder 或带 CAS 的 AtomicLong;复杂不变量需锁或不可变数据结构。确保“构造好的对象”对其他线程一次性、完整地可见:
**volatile**** 写**(把引用写入 volatile 字段)。ConcurrentHashMap、CopyOnWriteArrayList)。**final**** 字段**:只要对象构造期间没有把 this 逸出,final 字段对读者天然可见为构造后的值。反例:在构造函数中把 this 传给别的线程或注册到可被别的线程访问的全局结构,打破安全发布。
volatile 字段 + 本地缓存读取频率控制。AtomicLong;高并发下用 LongAdder(分段累加,读时合并)。BlockingQueue(内部用 AQS/锁/条件队列保障 HB)。**volatile** 就线程安全”:错。volatile ≠ 互斥,复合操作仍然竞态。final List,其内部元素仍可变。诊断手段
jfr/jcmd 观察线程状态(BLOCKED、RUNNABLE 自旋)-Xint/-Xmixed 对比乱序复现概率(仅辅助理解)volatile 写→读、锁解锁→加锁、start()/join()、传递性。**volatile**:可见 + 部分禁止重排;不提供互斥,复合操作仍需 CAS/锁。volatile 会因重排读到“半初始化对象”;正确解法是 volatile 或静态初始化/枚举。volatile、锁、静态初始化、并发容器、final 字段构造后不逸出。volatile 标志 vs 中断import java.util.concurrent.*;
public class CancelDemo {
static volatile boolean running = true;
public static void main(String[] args) throws Exception {
BlockingQueue<Integer> q = new ArrayBlockingQueue<>(1024);
Thread producer = new Thread(() -> {
int i = 0;
while (running) {
try { q.put(i++); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
Thread consumer = new Thread(() -> {
while (running) {
try { q.take(); }
catch (InterruptedException e) { Thread.currentThread().interrupt(); }
}
});
producer.start(); consumer.start();
TimeUnit.SECONDS.sleep(1);
running = false; // 通过 volatile 可见
producer.interrupt(); // 响应阻塞状态
consumer.interrupt();
producer.join(); consumer.join();
System.out.println("Stopped.");
}
}
要点:
volatile 标志停止;take/put),两者结合才可靠。AtomicLong vs LongAdderimport java.util.concurrent.*;
import java.util.concurrent.atomic.*;
public class CounterCompare {
static final int THREADS = 32;
static final int OPS = 1_000_00;
public static void main(String[] args) throws Exception {
benchAtomic();
benchAdder();
}
static void benchAtomic() throws Exception {
AtomicLong c = new AtomicLong();
run(() -> { for (int i = 0; i < OPS; i++) c.incrementAndGet(); }, "AtomicLong");
System.out.println(c.get());
}
static void benchAdder() throws Exception {
LongAdder c = new LongAdder();
run(() -> { for (int i = 0; i < OPS; i++) c.increment(); }, "LongAdder");
System.out.println(c.sum());
}
static void run(Runnable task, String label) throws Exception {
long t0 = System.nanoTime();
ExecutorService es = Executors.newFixedThreadPool(THREADS);
for (int i = 0; i < THREADS; i++) es.submit(task);
es.shutdown();
es.awaitTermination(1, TimeUnit.MINUTES);
long t1 = System.nanoTime();
System.out.printf("%s took %.2f ms%n", label, (t1 - t0) / 1e6);
}
}
预期:高并发下 LongAdder 更快(减少单点 CAS 冲突),但读取开销略高(需要合并)。
ReorderingDemo 中加入“无意义计算”或 Thread.onSpinWait(),观察 (0,0) 触发频率变化,写出结论。-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation 观察 getInstance() 的编译情况,解释为何 volatile 能阻止错误。Config{ final String a; final int b; },对比用 volatile 引用发布 vs 非 volatile 发布,设计测试证明读取一致性差异。volatile 能与不能。StoreLoad 最敏感;ARM 更弱,volatile 成本更高。Java 并发的本质是在弱内存模型上用 HB 建立跨线程的“可见 + 不乱序”关系。
volatile 负责轻量的可见/顺序,synchronized/CAS 负责原子性/互斥。
正确性优先、再谈性能,选择时遵循:标志/发布→volatile,简单计数→原子类/LongAdder,复杂不变量→锁或不可变设计。