Java并发编程之synchronized有序性误区

许多开发者误以为synchronized能完全禁止指令重排序,而实际上它仅能保证线程间的操作顺序,真正禁用重排序需依赖volatile


1. synchronized的有序性保证

synchronized通过锁机制间接保证多线程操作的顺序性,具体规则基于 Happens-Before 原则

  • 锁的释放(unlock)操作 Happens-Before 后续的锁获取(lock)操作
    这意味着:
    • 线程A在同步块内对共享变量的修改,对线程B在获取同一锁后的操作完全可见
    • 线程间的操作顺序在逻辑上按锁的获取顺序执行。
示例:线程间可见性与顺序性
public class SynchronizedOrderDemo {
    private int value = 0;
    private final Object lock = new Object();

    // 线程A:写操作
    public void write() {
        synchronized (lock) {
            value = 42; // 写操作
        }
    }

    // 线程B:读操作
    public int read() {
        synchronized (lock) {
            return value; // 保证读到42
        }
    }
}

执行顺序

  1. 线程A先获取锁,写value = 42,释放锁。
  2. 线程B获取锁后,一定能读到value = 42

2. synchronized无法禁止指令重排序

尽管synchronized保证了线程间的顺序性,但同步块内部的指令仍可能被重排序(只要不影响单线程执行结果)。这种重排序对单线程透明,但可能导致多线程问题。

示例:同步块内的指令重排序
synchronized (lock) {
    int a = 1;  // 操作1
    int b = 2;  // 操作2
}
  • 可能的执行顺序:操作2可能在操作1之前执行。
  • 单线程结果一致:无论顺序如何,最终a=1b=2
  • 多线程隐患:若其他线程依赖ab的写入顺序,可能导致逻辑错误。

1. volatile的禁止重排序机制

volatile通过插入内存屏障,直接限制编译器和处理器的指令重排序:

  • 写操作:插入 StoreStore屏障StoreLoad屏障
  • 读操作:插入 LoadLoad屏障LoadStore屏障
示例:volatile禁止对象初始化的重排序
// 双重检查锁定(DCL)单例模式
public class Singleton {
    private static volatile Singleton instance; // volatile禁止重排序

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton(); // 初始化操作
                }
            }
        }
        return instance;
    }
}
  • volatile的隐患
    对象初始化可能被重排序为:分配内存 → 返回引用 → 初始化对象。其他线程可能拿到未初始化的对象。
  • volatile的作用
    强制按顺序执行:分配内存 → 初始化对象 → 返回引用。

三、对比

特性synchronizedvolatile
线程顺序性保证多线程间的操作顺序(通过锁的Happens-Before规则)不直接保证多线程顺序,仅确保单变量操作顺序
指令重排序允许同步块内部的重排序(单线程语义下)禁止与volatile变量相关的重排序
可见性通过锁释放-获取保证所有变量的可见性仅保证volatile变量的可见性
原子性保证同步块内操作的原子性不保证复合操作的原子性(如i++
性能开销较高(涉及用户态与内核态切换)较低(仅内存屏障开销)

四、常见误区

1. 误区:synchronized能完全禁止指令重排序

  • 真相synchronized仅保证线程间的操作顺序,但同步块内的指令仍可能被重排序(不影响单线程结果即可)。
  • 示例
    若同步块内有两个无关变量的赋值,编译器可能调整其顺序。

2. 误区:volatile可替代锁

  • 错误示例
    private volatile int count = 0;
    public void increment() {
        count++; // 非原子操作,volatile无法保证线程安全
    }
    
  • 正确方案:使用AtomicIntegersynchronized
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值