volatile、synchronized和Lock

名词解释:

指令重排是计算机为了优化执行效率,在不改变单线程程序结果的前提下,对代码的执行顺序进行重新排列的操作。它可能发生在编译阶段(编译器优化)或CPU运行阶段(处理器优化)。


举个栗子🌰:做饭的步骤

假设你要做一道菜,步骤是:

  1. 洗锅 → 2. 热油 → 3. 放菜 → 4. 翻炒

指令重排后可能变成

  1. 洗锅 → 3. 放菜(未热油) → 2. 热油 → 4. 翻炒
    单线程下没问题(最终菜还是熟的),但多线程下可能翻车(其他线程看到“放菜”时油还没热)!

为什么需要指令重排?

  • 提高执行效率:CPU和编译器会通过重排指令,充分利用硬件资源(如并行执行不冲突的操作)。
  • 减少等待时间:避免因某些操作(如内存读取延迟)导致的空闲等待。

多线程环境中的问题

指令重排在单线程无感知,但在多线程并发时可能导致意外结果

经典案例:双重检查锁(DCL)单例模式

public class Singleton {
    private static Singleton instance;
    
    public static Singleton getInstance() {
        if (instance == null) {                    // 第一次检查
            synchronized (Singleton.class) {
                if (instance == null) {            // 第二次检查
                    instance = new Singleton();    // 问题在此!
                }
            }
        }
        return instance;
    }
}

问题分析
instance = new Singleton() 实际分为三步:

  1. 分配内存空间
  2. 初始化对象
  3. 将引用指向内存地址

指令重排可能导致步骤2和3颠倒
→ 其他线程可能在对象未初始化完成时,拿到非空的instance,导致使用错误!


如何禁止指令重排?

  1. 使用 volatile 关键字
    → 修饰变量(如 private volatile static Singleton instance;),通过插入内存屏障禁止重排序。
    → 解决上述DCL单例问题。

  2. 使用 synchronizedLock
    → 同步代码块保证原子性和可见性,隐含禁止重排序。


总结

  • 指令重排:优化手段,单线程安全,多线程需警惕。
  • 解决方案volatile 或同步机制确保多线程下的顺序一致性。

在 Java 中,volatilesynchronizedLock 是解决并发问题的三种重要工具。它们有不同的使用场景和特点,下面分别介绍它们的用途、常用方法以及适用场景。


1. volatile

用途

  • 保证可见性:确保一个线程对共享变量的修改对其他线程立即可见。
  • 防止指令重排序:通过插入内存屏障(Memory Barrier)禁止某些编译器或处理器的指令重排序优化。

特点

  • 只适用于单个变量。
  • 不保证复合操作(如x++)的原子性。
  • 开销较小,性能优于synchronized

常用场景

  • 用于状态标志位(如开关标志)。
  • 当只需要保证可见性和有序性时使用。

例子

class VolatileExample {
    private volatile boolean flag = true;

    public void stop() {
        flag = false; // 修改flag,其他线程会立即看到
    }

    public void run() {
        while (flag) {
            // 执行任务
        }
        System.out.println("Thread stopped");
    }
}

2. synchronized

用途

  • 保证互斥性:同一时刻只有一个线程可以执行被同步保护的代码块。
  • 保证可见性:当一个线程释放锁时,会将修改后的变量值刷新到主存中,其他线程获取锁时会从主存中读取最新值。
  • 防止指令重排序:通过插入内存屏障确保有序性。

特点

  • 可以作用于代码块或方法。
  • 提供了内置锁机制,简单易用。
  • 性能较低(相对volatile),但在复杂场景下更可靠。

常用方法/用法

  1. 同步方法

    • 使用synchronized修饰方法,锁定当前对象(即this)。
    public synchronized void increment() {
        count++;
    }
    
  2. 同步代码块

    • 使用synchronized修饰代码块,指定锁对象。
    public void increment() {
        synchronized (lock) {
            count++;
        }
    }
    
  3. 静态同步方法

    • 锁定的是类对象(Class实例)。
    public static synchronized void incrementStatic() {
        staticCount++;
    }
    

例子

class SynchronizedExample {
    private int count = 0;

    public synchronized void increment() {
        count++; // 线程安全
    }

    public synchronized int getCount() {
        return count;
    }
}

3. Lock(ReentrantLock)

用途

  • 提供比synchronized更灵活的锁机制
    • 支持公平锁和非公平锁。
    • 支持尝试获取锁(tryLock())。
    • 支持可中断锁(lockInterruptibly())。
    • 支持超时获取锁(tryLock(long timeout, TimeUnit unit))。
  • 保证互斥性、可见性和有序性

特点

  • 需要手动加锁和解锁(容易忘记解锁,导致死锁)。
  • 提供更多功能,但使用复杂度更高。
  • 性能通常优于synchronized(尤其是在高竞争情况下)。

常用方法

  1. 核心接口:java.util.concurrent.locks.Lock

    • void lock():获取锁,如果锁不可用则阻塞。
    • void unlock():释放锁。
    • boolean tryLock():尝试获取锁,成功返回true,失败返回false
    • boolean tryLock(long timeout, TimeUnit unit):尝试在指定时间内获取锁。
    • void lockInterruptibly():获取锁,但可以响应中断。
  2. 实现类:ReentrantLock

    • 可重入锁,支持公平锁和非公平锁。

例子

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class LockExample {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock(); // 加锁
        try {
            count++; // 线程安全
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

对比总结

特性volatilesynchronizedLock(ReentrantLock)
互斥性不支持支持支持
可见性支持支持支持
有序性支持(禁止重排序)支持支持
原子性不支持复合操作支持支持
灵活性
性能高(高竞争下优于synchronized
适用场景单个变量的状态标志方法或代码块的同步需要高级功能(如尝试锁、公平锁等)

选择建议

  1. 优先使用volatile
    • 如果只需要保证可见性和有序性,并且不涉及复合操作。
  2. 使用synchronized
    • 如果需要简单的互斥性、可见性和有序性,且不需要额外的功能。
  3. 使用Lock
    • 如果需要更灵活的锁机制(如尝试锁、超时锁、公平锁等)。

通过合理选择工具,可以在保证线程安全的同时,提升程序的性能和可维护性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值