在Java并发编程中,volatile
和 synchronized
是两种常见的同步机制。它们虽然都可以用于多线程环境下的变量同步,但在语义和使用场景上有显著区别。以下是详细的对比与补充:
volatile关键字
volatile
关键字用于声明共享变量,并为其赋予两层语义:
-
可见性保证:
- 当一个线程修改了
volatile
变量的值,新值会立即被刷新到主内存中,并且其他线程读取这个变量时可以立即获得最新的值。 - 具体来说,当一个线程写入
volatile
变量时,会强制将缓存中的数据刷新到主内存;当一个线程读取volatile
变量时,会强制从主内存中读取数据。
- 当一个线程修改了
-
禁止指令重排序:
volatile
变量在写操作和读操作时,会在指令层面加上内存屏障(Memory Barrier),以禁止对这些操作进行重排序,从而避免因重排序带来的数据不一致问题。
synchronized关键字
synchronized
关键字可以用于方法或者代码块,实现同步。其主要特点包括:
-
可见性和原子性保证:
synchronized
不仅能保证变量的可见性,还能保证变量操作的原子性。- 进入
synchronized
方法或代码块的线程会持有锁,任何其他线程在锁被释放前都无法进入同一个synchronized
方法或代码块。
-
线程阻塞:
- 使用
synchronized
关键字时,如果一个线程获取了锁,其他线程试图获取这把锁时会被阻塞,直到锁被释放。
- 使用
对比总结
-
使用层级:
volatile
:只能用于变量级别。synchronized
:可以用于方法级别和代码块级别(包括类和实例级别)。
-
功能:
volatile
:只能保证可见性,不能保证操作的原子性。例如,volatile
不能保证复合操作(如i++)的原子性。synchronized
:不仅能保证可见性,还能保证操作的原子性。
-
性能:
volatile
:不会造成线程阻塞,性能开销较小,因为它不会涉及上下文切换和调度。synchronized
:可能会造成线程阻塞,导致上下文切换和调度,性能开销相对较大。
-
编译器优化:
volatile
:变量不会被编译器优化,必须每次都从主内存中读取。synchronized
:在锁释放之前,编译器和处理器都不能对其进行优化,但锁释放之后,相关代码仍可被优化。public class VolatileExample { private volatile boolean flag = false; public void writer() { flag = true; // 写操作 } public void reader() { if (flag) { // 读操作 // 进行操作 } } }
public class SynchronizedExample { private int count = 0; public synchronized void increment() { count++; // 原子性操作 } public synchronized int getCount() { return count; // 可见性保证 } }