MST

星途 面试题库

面试题:Java中volatile与synchronized在内存语义上的区别

从Java内存模型角度出发,分析volatile关键字和synchronized关键字在内存语义方面的区别。比如在保证可见性、原子性以及有序性上两者分别是如何实现的,在实际多线程编程场景中,应如何依据具体需求合理选择使用这两种机制。
40.4万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

1. 可见性

  • volatile
    • 当一个变量被声明为volatile时,线程对该变量的写操作会立即刷新到主内存,而读操作会直接从主内存中读取最新值。这是通过在汇编层面使用lock前缀指令,该指令会触发缓存一致性协议,确保其他处理器缓存中的该变量副本失效,从而保证了可见性。
  • synchronized
    • 在进入synchronized块时,会从主内存中读取共享变量的值,而在退出synchronized块时,会将共享变量的值刷新回主内存。这是因为synchronized的内存语义遵循Java内存模型的管程(Monitor)规则,管程的进入和退出会产生happen - before关系,保证了可见性。

2. 原子性

  • volatile
    • volatile只能保证单个读/写操作的原子性,对于复合操作(如i++,它包含读取、增加和写入三个操作)无法保证原子性。
  • synchronized
    • synchronized关键字通过对代码块或方法加锁,同一时刻只有一个线程能进入被synchronized修饰的代码块或方法,从而保证了该代码块或方法内操作的原子性。

3. 有序性

  • volatile
    • volatile关键字通过内存屏障来禁止指令重排序。写操作前插入StoreStore屏障,保证前面的普通写操作先于volatile写操作对其他线程可见;写操作后插入StoreLoad屏障,防止volatile写操作与后续读操作重排序。读操作后插入LoadLoad和LoadStore屏障,防止volatile读操作与后续普通读/写操作重排序。这保证了volatile变量相关操作的有序性。
  • synchronized
    • 由于synchronized通过管程实现,管程的进入和退出产生的happen - before关系,不仅保证了可见性,也保证了有序性。即monitorenter之前的操作happen - beforemonitorexit之后的操作。

4. 选择使用场景

  • volatile
    • 适用于读多写少的场景,当只需要保证变量的可见性和单个读/写操作的原子性,且不存在复合操作时,使用volatile可以减少同步开销,提高性能。例如,用于控制多线程中的开关状态变量。
  • synchronized
    • 适用于需要保证原子性、可见性和有序性的场景,特别是涉及复合操作或需要保证整个代码块线程安全的情况。例如,银行转账操作,涉及多个变量的修改,需要使用synchronized来保证操作的完整性。