第5部分-Java内存模型与可见性

第5部分:Java内存模型与可见性

核心目标

掌握并发问题的底层根源。


1. Java内存模型(JMM)详解

JMM基本概念

Java内存模型(Java Memory Model,JMM)是Java虚拟机规范中定义的一种内存访问规范,它定义了程序中各个变量的访问规则,以及在JVM中将变量存储到内存和从内存中取出变量这样的底层细节。

JMM结构图

┌─────────────────────────────────────────────────────────────┐
│                        JVM内存结构                           │
├─────────────────────────────────────────────────────────────┤
│  主内存 (Main Memory)                                       │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐          │
│  │   实例变量   │ │   静态变量   │ │   数组元素   │          │
│  │   (堆内存)   │ │   (方法区)   │ │   (堆内存)   │          │
│  └─────────────┘ └─────────────┘ └─────────────┘          │
│                                                             │
│  ┌─────────────────────────────────────────────────────────┐ │
│  │              线程间共享的变量                            │ │
│  └─────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────┤
│  线程1工作内存         线程2工作内存         线程N工作内存    │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐   │
│  │ 变量副本     │     │ 变量副本     │     │ 变量副本     │   │
│  │ 局部变量     │     │ 局部变量     │     │ 局部变量     │   │
│  │ 程序计数器   │     │ 程序计数器   │     │ 程序计数器   │   │
│  │ 操作数栈     │     │ 操作数栈     │     │ 操作数栈     │   │
│  └─────────────┘     └─────────────┘     └─────────────┘   │
└─────────────────────────────────────────────────────────────┘

内存交互操作

public class JMMExample {
    private int sharedVariable = 0;
    
    public void thread1() {
        // 1. read: 从主内存读取sharedVariable到工作内存
        // 2. load: 将读取的值放入工作内存的变量副本
        // 3. use: 使用变量副本进行计算
        int temp = sharedVariable;
        temp = temp + 1;
        // 4. assign: 将计算结果赋值给工作内存的变量副本
        sharedVariable = temp;
        // 5. store: 将工作内存的变量副本传送到主内存
        // 6. write: 将store操作从工作内存得到的变量值放入主内存的变量中
    }
    
    public void thread2() {
        // 同样的8个操作步骤
        int temp = sharedVariable;
        temp = temp + 1;
        sharedVariable = temp;
    }
}

2. 主内存与工作内存关系

内存层次结构

public class MemoryHierarchy {
    // 主内存中的变量
    private static int staticVar = 0;        // 方法区(主内存)
    private int instanceVar = 0;             // 堆内存(主内存)
    private int[] array = new int[10];       // 堆内存(主内存)
    
    public void memoryAccess() {
        // 局部变量在工作内存中
        int localVar = 0;                    // 虚拟机栈(工作内存)
        
        // 访问主内存变量
        staticVar++;                         // 需要从主内存读取,修改后写回
        instanceVar++;                       // 需要从主内存读取,修改后写回
        array[0] = 1;                        // 需要从主内存读取,修改后写回
        
        // 局部变量操作
        localVar++;                          // 直接在工作内存中操作
    }
}

内存可见性问题

public class VisibilityProblem {
    private boolean flag = false;
    private int count = 0;
    
    // 线程1执行
    public void thread1() {
        count = 1;           // 1. 写入工作内存
        flag = true;         // 2. 写入工作内存
        // 注意:工作内存的修改可能不会立即同步到主内存
    }
    
    // 线程2执行
    public void thread2() {
        if (flag) {          // 3. 从主内存读取flag(可能是旧值)
            int temp = count; // 4. 从主内存读取count(可能是旧值)
            System.out.println("Count: " + temp); // 可能输出0
        }
    }
}

内存同步机制

public class MemorySynchronization {
    private int value = 0;
    private volatile boolean ready = false;
    
    // 使用synchronized保证内存同步
    public synchronized void synchronizedMethod() {
        value = 42;
        ready = true;
        // synchronized块结束时会强制将工作内存刷新到主内存
    }
    
    public synchronized int getValue() {
        // synchronized块开始时会从主内存重新读取变量
        return value;
    }
    
    // 使用volatile保证内存同步
    public void volatileMethod() {
        value = 42;
        ready = true; // volatile写操作会立即刷新到主内存
    }
    
    public int getVolatileValue() {
        if (ready) { // volatile读操作会从主内存读取最新值
            return value;
        }
        return 0;
    }
}

3. Happens-Before规则

Happens-Before定义

Happens-Before是JMM中定义的一个偏序关系,用于描述两个操作之间的可见性关系。如果操作A happens-before 操作B,那么操作A的结果对操作B可见。

基本规则

public class HappensBeforeRules {
    private int x = 0;
    private int y = 0;
    private volatile boolean flag = false;
    
    // 规则1:程序顺序规则
    public void programOrderRule() {
        x = 1;        // 操作1
        y = 2;        // 操作2
        // 操作1 happens-before 操作2
    }
    
    // 规则2:volatile变量规则
    public void volatileRule() {
        x = 1;        // 操作1
        flag = true;  // 操作2 (volatile写)
        // 操作1 happens-before 操作2
        
        // 在另一个线程中
        if (flag) {   // 操作3 (volatile读)
            int temp = x; // 操作4
            // 操作2 happens-before 操作3
            // 操作1 happens-before 操作4 (通过传递性)
        }
    }
    
    // 规则3:传递性规则
    public void transitivityRule() {
        // 如果 A happens-before B,B happens-before C
        // 那么 A happens-before C
    }
    
    // 规则4:锁规则
    public synchronized void lockRule() {
        x = 1;        // 操作1
        // 解锁操作 happens-before 后续的加锁操作
    }
    
    public synchronized void anotherLockMethod() {
        int temp = x; // 操作2
        // 操作1 happens-before 操作2
    }
}

线程启动规则

public class ThreadStartRule {
    private int x = 0;
    
    public void threadStartExample() {
        x = 42; // 操作1
        
        Thread thread = new Thread(() -> {
            int temp = x; // 操作2
            System.out.println("x = " + temp); // 输出42
        });
        
        thread.start(); // 操作3
        
        // 操作1 happens-before 操作3
        // 操作3 happens-before 操作2
        // 因此操作1 happens-before 操作2
    }
}

线程终止规则

public class ThreadTerminationRule {
    private int result = 0;
    
    public void threadTerminationExample() throws InterruptedException {
        Thread thread = new Thread(() -> {
            result = 42; // 操作1
        });
        
        thread.start();
        thread.join(); // 操作2
        
        int temp = result; // 操作3
        System.out.println("result = " + temp); // 输出42
        
        // 操作1 happens-before 操作2
        // 操作2 happens-before 操作3
        // 因此操作1 happens-before 操作3
    }
}

中断规则

public class InterruptRule {
    private int x = 0;
    
    public void interruptExample() throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                x = 42; // 操作1
                Thread.currentThread().interrupt();
            }
        });
        
        thread.start();
        thread.interrupt(); // 操作2
        
        thread.join();
        int temp = x; // 操作3
        
        // 操作2 happens-before 操作1
        // 操作1 happens-before 操作3
    }
}

4. volatile关键字的语义与限制

volatile的基本语义

public class VolatileSemantics {
    private volatile boolean flag = false;
    private volatile int count = 0;
    
    // 1. 可见性保证
    public void visibilityExample() {
        // 线程1
        count = 1;     // volatile写,立即刷新到主内存
        flag = true;   // volatile写,立即刷新到主内存
        
        // 线程2
        if (flag) {    // volatile读,从主内存读取最新值
            int temp = count; // 能读取到最新的count值
        }
    }
    
    // 2. 禁止重排序
    public void reorderingExample() {
        int a = 1;
        int b = 2;
        flag = true;   // volatile写,不能与前面的操作重排序
        int c = a + b; // 不能与volatile写重排序
    }
}

volatile的内存屏障

public class VolatileMemoryBarrier {
    private int x = 0;
    private int y = 0;
    private volatile boolean flag = false;
    
    public void memoryBarrierExample() {
        // 在volatile写之前插入StoreStore屏障
        x = 1;        // 普通写
        y = 2;        // 普通写
        // StoreStore屏障
        flag = true;  // volatile写
        // StoreLoad屏障
        
        // 在volatile读之后插入LoadLoad和LoadStore屏障
        if (flag) {   // volatile读
            // LoadLoad屏障
            // LoadStore屏障
            int temp = x; // 普通读
            int temp2 = y; // 普通读
        }
    }
}

volatile的限制

public class VolatileLimitations {
    private volatile int count = 0;
    
    // 1. volatile不能保证原子性
    public void atomicityProblem() {
        // 这个操作不是原子的
        count++; // 等价于 count = count + 1
        // 包含:读取count、计算count+1、写入count三个步骤
        // 多个线程同时执行时仍可能出现竞态条件
    }
    
    // 2. volatile不能保证复合操作的原子性
    public void compoundOperationProblem() {
        // 这个操作不是原子的
        count = count * 2 + 1;
        // 包含多个步骤,volatile无法保证整个操作的原子性
    }
    
    // 3. volatile的正确使用场景
    private volatile boolean shutdown = false;
    
    public void correctVolatileUsage() {
        // 作为状态标志
        while (!shutdown) {
            // 执行任务
        }
    }
    
    public void shutdown() {
        shutdown = true; // 简单的状态改变
    }
    
    // 4. 单例模式中的volatile使用
    private static volatile VolatileLimitations instance;
    
    public static VolatileLimitations getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (VolatileLimitations.class) {
                if (instance == null) { // 第二次检查
                    instance = new VolatileLimitations();
                }
            }
        }
        return instance;
    }
}

volatile vs synchronized

public class VolatileVsSynchronized {
    private volatile int volatileCount = 0;
    private int synchronizedCount = 0;
    
    // volatile方式
    public void volatileIncrement() {
        volatileCount++; // 不是原子操作
    }
    
    // synchronized方式
    public synchronized void synchronizedIncrement() {
        synchronizedCount++; // 原子操作
    }
    
    // 性能对比
    public void performanceComparison() {
        long start = System.currentTimeMillis();
        
        // volatile操作
        for (int i = 0; i < 1000000; i++) {
            volatileIncrement();
        }
        
        long volatileTime = System.currentTimeMillis() - start;
        
        start = System.currentTimeMillis();
        
        // synchronized操作
        for (int i = 0; i < 1000000; i++) {
            synchronizedIncrement();
        }
        
        long synchronizedTime = System.currentTimeMillis() - start;
        
        System.out.println("Volatile time: " + volatileTime + "ms");
        System.out.println("Synchronized time: " + synchronizedTime + "ms");
    }
}

5. 指令重排序与内存屏障

指令重排序

public class InstructionReordering {
    private int a = 0;
    private int b = 0;
    private int x = 0;
    private int y = 0;
    
    // 线程1
    public void thread1() {
        a = 1;  // 操作1
        x = b;  // 操作2
        // 编译器或处理器可能重排序为:x = b; a = 1;
    }
    
    // 线程2
    public void thread2() {
        b = 1;  // 操作3
        y = a;  // 操作4
        // 编译器或处理器可能重排序为:y = a; b = 1;
    }
    
    // 重排序可能导致的问题
    public void reorderingProblem() {
        // 如果重排序发生,可能出现:
        // x = 0, y = 0 的情况
        // 这在顺序执行中是不可能的
    }
}

内存屏障类型

public class MemoryBarrierTypes {
    private int x = 0;
    private int y = 0;
    private volatile boolean flag = false;
    
    // 1. LoadLoad屏障
    public void loadLoadBarrier() {
        int temp1 = x; // Load1
        // LoadLoad屏障:确保Load1在Load2之前完成
        int temp2 = y; // Load2
    }
    
    // 2. StoreStore屏障
    public void storeStoreBarrier() {
        x = 1; // Store1
        // StoreStore屏障:确保Store1在Store2之前完成
        y = 2; // Store2
    }
    
    // 3. LoadStore屏障
    public void loadStoreBarrier() {
        int temp = x; // Load
        // LoadStore屏障:确保Load在Store之前完成
        y = 1; // Store
    }
    
    // 4. StoreLoad屏障
    public void storeLoadBarrier() {
        x = 1; // Store
        // StoreLoad屏障:确保Store在Load之前完成
        int temp = y; // Load
    }
    
    // volatile的内存屏障
    public void volatileBarriers() {
        x = 1;        // 普通写
        y = 2;        // 普通写
        // StoreStore屏障
        flag = true;  // volatile写
        // StoreLoad屏障
        
        if (flag) {   // volatile读
            // LoadLoad屏障
            // LoadStore屏障
            int temp = x; // 普通读
        }
    }
}

重排序规则

public class ReorderingRules {
    private int a = 0;
    private int b = 0;
    private volatile int volatileVar = 0;
    
    // 规则1:as-if-serial语义
    public void asIfSerialRule() {
        int temp1 = a;  // 操作1
        int temp2 = b;  // 操作2
        int result = temp1 + temp2; // 操作3
        
        // 操作1和操作2可以重排序,但操作3不能与操作1、2重排序
        // 因为操作3依赖于操作1和操作2的结果
    }
    
    // 规则2:happens-before规则
    public void happensBeforeRule() {
        a = 1;          // 操作1
        volatileVar = 1; // 操作2 (volatile写)
        // 操作1不能与操作2重排序
        
        if (volatileVar == 1) { // 操作3 (volatile读)
            int temp = a; // 操作4
            // 操作3不能与操作4重排序
        }
    }
    
    // 规则3:数据依赖规则
    public void dataDependencyRule() {
        int temp = a;   // 操作1
        int result = temp + 1; // 操作2
        // 操作2依赖于操作1的结果,不能重排序
    }
}

6. final字段的线程安全语义

final字段的基本语义

public class FinalFieldSemantics {
    private final int finalField;
    private int normalField;
    
    public FinalFieldSemantics(int value) {
        this.normalField = value;
        this.finalField = value; // final字段必须在构造函数中初始化
    }
    
    // final字段的线程安全保证
    public void finalFieldSafety() {
        // 1. final字段在构造函数完成后对其他线程可见
        // 2. final字段不会被重排序到构造函数之外
        // 3. 引用类型的final字段,其引用的对象内容可能不是线程安全的
    }
}

final字段的重排序规则

public class FinalFieldReordering {
    private final int finalField;
    private int normalField;
    
    public FinalFieldReordering(int value) {
        this.normalField = value; // 操作1
        this.finalField = value;  // 操作2
        
        // 操作1和操作2可以重排序
        // 但finalField的写入不能重排序到构造函数之外
    }
    
    // 读取final字段
    public void readFinalField() {
        int temp = finalField; // 能读取到正确的值
        int temp2 = normalField; // 可能读取到0(默认值)
    }
}

final引用字段

public class FinalReferenceField {
    private final int[] finalArray;
    private final List<String> finalList;
    
    public FinalReferenceField() {
        this.finalArray = new int[10];
        this.finalList = new ArrayList<>();
        
        // final引用字段的引用是线程安全的
        // 但引用指向的对象内容可能不是线程安全的
    }
    
    public void modifyFinalReference() {
        // 不能重新赋值final引用
        // this.finalArray = new int[20]; // 编译错误
        
        // 但可以修改引用指向的对象内容
        this.finalArray[0] = 1; // 需要额外的同步机制保证线程安全
        this.finalList.add("item"); // 需要额外的同步机制保证线程安全
    }
}

final字段的初始化安全性

public class FinalFieldInitializationSafety {
    private final int finalField;
    private int normalField;
    
    public FinalFieldInitializationSafety(int value) {
        this.normalField = value;
        this.finalField = value;
    }
    
    // 静态final字段
    private static final int STATIC_FINAL = 42;
    
    // 静态final字段的初始化
    static {
        // 静态final字段在类初始化时设置
        // 对后续的所有线程都可见
    }
    
    // 读取final字段
    public int getFinalField() {
        return finalField; // 总是能读取到正确的值
    }
    
    public int getNormalField() {
        return normalField; // 可能读取到0(默认值)
    }
}

实践练习

练习1:内存可见性测试

public class VisibilityTest {
    private boolean flag = false;
    private int count = 0;
    
    public void testVisibility() throws InterruptedException {
        // 线程1:修改共享变量
        Thread writer = new Thread(() -> {
            count = 42;
            flag = true;
            System.out.println("Writer: count=" + count + ", flag=" + flag);
        });
        
        // 线程2:读取共享变量
        Thread reader = new Thread(() -> {
            while (!flag) {
                // 空循环,等待flag变为true
            }
            System.out.println("Reader: count=" + count + ", flag=" + flag);
        });
        
        reader.start();
        writer.start();
        
        writer.join();
        reader.join();
    }
    
    // 使用volatile解决可见性问题
    private volatile boolean volatileFlag = false;
    private volatile int volatileCount = 0;
    
    public void testVolatileVisibility() throws InterruptedException {
        Thread writer = new Thread(() -> {
            volatileCount = 42;
            volatileFlag = true;
            System.out.println("Writer: count=" + volatileCount + ", flag=" + volatileFlag);
        });
        
        Thread reader = new Thread(() -> {
            while (!volatileFlag) {
                // 空循环
            }
            System.out.println("Reader: count=" + volatileCount + ", flag=" + volatileFlag);
        });
        
        reader.start();
        writer.start();
        
        writer.join();
        reader.join();
    }
}

练习2:指令重排序测试

public class ReorderingTest {
    private int x = 0;
    private int y = 0;
    private int a = 0;
    private int b = 0;
    
    public void testReordering() throws InterruptedException {
        int count = 0;
        int reorderCount = 0;
        
        for (int i = 0; i < 100000; i++) {
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            
            Thread thread1 = new Thread(() -> {
                a = 1;
                x = b;
            });
            
            Thread thread2 = new Thread(() -> {
                b = 1;
                y = a;
            });
            
            thread1.start();
            thread2.start();
            
            thread1.join();
            thread2.join();
            
            count++;
            if (x == 0 && y == 0) {
                reorderCount++;
                System.out.println("重排序发生!x=" + x + ", y=" + y);
            }
        }
        
        System.out.println("总次数: " + count);
        System.out.println("重排序次数: " + reorderCount);
        System.out.println("重排序概率: " + (double) reorderCount / count * 100 + "%");
    }
}

练习3:Happens-Before规则验证

public class HappensBeforeVerification {
    private int x = 0;
    private volatile boolean flag = false;
    
    public void verifyHappensBefore() throws InterruptedException {
        Thread writer = new Thread(() -> {
            x = 42;        // 操作1
            flag = true;   // 操作2 (volatile写)
        });
        
        Thread reader = new Thread(() -> {
            while (!flag) {
                // 等待flag变为true
            }
            // 操作2 happens-before 这里的操作
            int temp = x;  // 操作3
            System.out.println("x = " + temp); // 应该输出42
        });
        
        reader.start();
        writer.start();
        
        writer.join();
        reader.join();
    }
}

总结

本部分深入介绍了Java内存模型与可见性的核心概念:

  1. JMM结构:理解主内存与工作内存的关系
  2. Happens-Before规则:掌握操作间的可见性关系
  3. volatile关键字:了解volatile的语义与限制
  4. 指令重排序:理解重排序的原因与影响
  5. 内存屏障:掌握内存屏障的作用
  6. final字段:了解final字段的线程安全语义

这些知识是理解并发编程底层原理的关键,下一部分将学习无锁编程与原子操作。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁在黄金彼岸

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值