面试题答案
一键面试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 - before
于monitorexit
之后的操作。
- 由于
4. 选择使用场景
- volatile:
- 适用于读多写少的场景,当只需要保证变量的可见性和单个读/写操作的原子性,且不存在复合操作时,使用
volatile
可以减少同步开销,提高性能。例如,用于控制多线程中的开关状态变量。
- 适用于读多写少的场景,当只需要保证变量的可见性和单个读/写操作的原子性,且不存在复合操作时,使用
- synchronized:
- 适用于需要保证原子性、可见性和有序性的场景,特别是涉及复合操作或需要保证整个代码块线程安全的情况。例如,银行转账操作,涉及多个变量的修改,需要使用
synchronized
来保证操作的完整性。
- 适用于需要保证原子性、可见性和有序性的场景,特别是涉及复合操作或需要保证整个代码块线程安全的情况。例如,银行转账操作,涉及多个变量的修改,需要使用