面试题答案
一键面试使用场景
- volatile:适用于当一个变量需要在线程间可见,且不涉及复杂的同步操作,如状态标记变量。例如,在一个多线程的程序中,一个线程负责检查某个任务是否结束,另一个线程在任务完成时修改这个结束标志变量,使用
volatile
能保证检查线程及时看到标志的变化。 - synchronized:用于保护一段代码块或方法,确保同一时间只有一个线程能进入该代码块或执行该方法,适用于对共享资源进行读写操作,且需要保证数据一致性的场景。例如银行转账操作,对账户余额的修改需要保证原子性,就可以使用
synchronized
。
原理
- volatile:通过内存屏障来实现可见性。当一个变量被声明为
volatile
时,写操作会在写变量后插入一个写屏障,强制将修改后的值刷新到主内存;读操作会在读变量前插入一个读屏障,强制从主内存读取最新值。但volatile
不保证原子性,对于像i++
这种复合操作,多个线程同时执行可能会出现数据不一致。 - synchronized:基于对象监视器(Monitor)实现。当线程进入
synchronized
修饰的代码块或方法时,会获取对象的监视器锁,其他线程就无法获取该锁,从而实现同步。锁的获取和释放过程会导致线程上下文切换。
性能差异
- volatile:由于不涉及锁的获取和释放,没有线程上下文切换的开销,性能相对较高,适合读多写少的场景。
- synchronized:获取和释放锁会带来一定的性能开销,尤其是在高并发场景下,大量线程竞争锁会导致频繁的上下文切换,性能会显著下降。
选择原因
- 选择volatile而不是synchronized:
- 当只需要保证变量的可见性,不需要保证操作的原子性时,
volatile
开销更小。例如一个用于控制线程是否停止的标志位,只需要线程能及时看到其变化,不需要保证对其修改的原子性。 - 在读多写少的场景中,
volatile
能提供更好的性能,因为它避免了锁竞争带来的开销。
- 当只需要保证变量的可见性,不需要保证操作的原子性时,
- 选择synchronized而不是volatile:
- 当对共享资源的操作需要保证原子性时,必须使用
synchronized
。如对共享变量的复杂计算操作,volatile
无法满足原子性要求。 - 在写操作较多且需要保证数据一致性的场景下,
synchronized
能通过锁机制确保操作的顺序性和原子性,虽然性能开销大,但能保证数据的正确性。
- 当对共享资源的操作需要保证原子性时,必须使用