并发编程(二)
volatile的目的与作用volatile关键字主要用于解决多线程环境下的两个核心问题内存可见性问题确保变量的修改对所有线程立即可见指令重排序问题禁止编译器对该变量相关的指令进行重排序优化内存可见性问题详解在多线程编程中非volatile变量可能被缓存在CPU寄存器中线程本地内存(工作内存)中这会导致一个线程的修改对其他线程不可见。例如// 没有volatile修饰时可能出现问题 boolean running true; void worker() { while(running) { // 工作代码 } } void stop() { running false; // 修改可能不会立即对其他线程可见 }volatile通过强制所有读写操作直接访问主内存来解决这个问题确保修改立即对所有线程可见。volatile的底层实现原理内存屏障机制volatile的实现依赖于内存屏障(Memory Barrier)写屏障(Store Barrier)确保volatile写之前的操作不会重排序到写之后读屏障(Load Barrier)确保volatile读之后的操作不会重排序到读之前缓存一致性协议如MESI协议(Modified, Exclusive, Shared, Invalid)确保多核CPU缓存的一致性x86架构下的具体实现在x86架构下volatile的写操作会生成带有lock前缀的指令这会触发缓存一致性协议使其他CPU核心的缓存行失效例如lock addl $0x0,(%esp)指令这种机制保证了多线程环境下变量的可见性。volatile与happens-before关系volatile变量建立了严格的happens-before关系写操作happens-before规则对volatile变量的写操作happens-before后续对该变量的读操作线程启动规则线程A中写volatile变量然后启动线程B线程B能看到线程A的修改线程终止规则线程A写volatile变量后终止线程B启动后能看到线程A的修改这种顺序性保证使得volatile可以用于实现简单的线程间通信。volatile的局限性volatile虽然强大但也有其局限性原子性问题volatile不能保证复合操作的原子性。例如volatile int count 0; // 以下操作在多线程环境下仍然不安全 count; // 实际上包含三个步骤 // 1. 读取count值 // 2. 增加1 // 3. 写回count对于这种复合操作需要配合以下方式使用synchronized关键字AtomicInteger等原子类Lock接口的实现类适用场景限制volatile不适用于需要多个变量共同参与原子操作的场景需要基于当前值进行条件判断的场景需要实现复杂同步逻辑的场景volatile的典型使用场景1. 状态标志位最典型的应用是作为线程退出的标志public class WorkerThread extends Thread { private volatile boolean running true; public void run() { while(running) { // 执行任务 } } public void stopWorker() { running false; } }2. 一次性安全发布适用于单次写入、多次读取的场景class Singleton { private volatile static Singleton instance; public static Singleton getInstance() { if (instance null) { synchronized(Singleton.class) { if (instance null) { instance new Singleton(); } } } return instance; } }3. 独立观察模式定期发布观察结果供其他线程使用class TemperatureMonitor { private volatile int currentTemperature; public void updateTemperature(int temp) { currentTemperature temp; } // 其他线程可以安全读取最新温度 }volatile的性能考虑性能特点读操作比普通变量稍慢因为需要绕过缓存但比锁机制快得多写操作比普通变量慢因为需要刷新到主内存但比锁机制快适用场景volatile在以下场景性能表现良好读多写少如状态标志位轻量级同步简单的线程间通信替代部分锁场景当操作本身已经是原子性时不适用场景在以下场景性能较差频繁写入会导致大量缓存失效复合操作需要额外的同步机制最佳实践建议明确使用场景只在需要保证可见性时使用避免滥用不要将所有共享变量都声明为volatile配合其他机制复杂场景结合锁或原子类使用性能测试在高并发场景下进行性能评估文档说明对volatile变量的使用意图进行注释synchronized 的基本概念与原理定义与作用synchronized是 Java 中最基本的线程同步机制主要用于解决多线程环境下的并发问题。它的核心作用包括线程同步确保同一时刻只有一个线程可以访问被保护的代码或资源原子性保证被修饰的代码块或方法作为一个不可分割的整体执行可见性确保一个线程对共享变量的修改能够立即对其他线程可见有序性防止指令重排序导致的并发问题例如在银行转账场景中使用synchronized可以确保转账操作的原子性防止出现一个账户扣款成功但另一个账户未到账的情况。底层实现JVM 通过**监视器锁Monitor**机制实现synchronized主要涉及以下部分对象头中的 Mark Word每个 Java 对象都有一个对象头其中包含 Mark WordMark Word 存储了对象的哈希码、分代年龄和锁标志位等信息锁状态变化时Mark Word 的内容会相应改变锁升级过程偏向锁Biased Locking适用于只有一个线程访问同步块的场景通过 CAS 操作在 Mark Word 中记录线程 ID轻量级锁Lightweight Locking当有轻微竞争时通过 CAS 和自旋尝试获取锁重量级锁Heavyweight Locking竞争激烈时线程会进入阻塞状态由操作系统进行线程调度锁升级是 JVM 为了提高并发性能而设计的优化策略会根据实际竞争情况动态调整锁的级别。synchronized 的加锁方式实例方法加锁public synchronized void instanceMethod() { // 同步代码 }锁对象是当前实例this适用于需要对实例变量进行同步保护的场景不同实例之间的同步互不影响示例场景银行账户类中的转账方法需要确保同一账户的转账操作是线程安全的。静态方法加锁public static synchronized void staticMethod() { // 同步代码 }锁对象是类的 Class 对象如MyClass.class适用于需要对类级别资源进行同步保护的场景所有实例共享同一个锁示例场景计数器类中的静态计数器需要确保所有线程对计数器的访问是同步的。代码块加锁public void method() { // 非同步代码 synchronized(lockObject) { // 同步代码块 } // 非同步代码 }锁对象可以是任意对象实例或 Class 对象提供了更细粒度的同步控制可以指定不同的锁对象来实现更灵活的同步策略示例场景使用特定对象作为锁private final Object lock new Object(); public void method() { synchronized(lock) { // 同步代码 } }使用 Class 对象作为锁与静态方法加锁效果相同public void method() { synchronized(MyClass.class) { // 同步代码 } }代码块加锁方式相比方法级加锁更加灵活可以减少锁的粒度提高并发性能。