故事背景
小米是一家互联网大厂的Java开发工程师,最近在准备面试题。他的朋友小明在另一家公司面试Java后端工程师,面试回来一脸生无可恋:“面试官上来就问 synchronized 和 volatile 的区别,我结巴了半天,最后被怼得体无完肤!” 小米哈哈大笑:“这个问题很好回答啊,我来给你讲讲。” 于是,一场关于 Java 并发的讨论就此展开……
图片
synchronized 和 volatile 的基本概念
小米清了清嗓子,开始讲解:
“我们写 Java 代码时,多线程编程是绕不开的一个话题,而 synchronized 和 volatile 这两个关键字,都是 Java 提供的线程同步手段。”
1、synchronized:独占锁,保证原子性和可见性
- synchronized 是 Java 提供的关键字,它表示只有一个线程可以获取作用对象的锁,进入同步代码块,其他线程必须等待。
- 这样可以防止多个线程并发修改共享资源,保证变量的可见性和原子性,防止线程安全问题。
2、volatile:内存可见性,防止指令重排序
volatile 关键字用于修饰变量,它告诉 CPU 和编译器:
- 这个变量不能被缓存,必须从主存(内存)中读取。
- 禁止指令重排序,防止CPU对代码执行顺序进行优化,导致多线程环境下的程序异常。
小明点点头:“听起来好像都能用来解决并发问题啊?它们的区别到底在哪?”
synchronized 和 volatile 的核心区别
小米笑着说:“这正是面试官的坑点!虽然它们都跟线程安全有关,但作用完全不同。”
1、修饰对象的范围不同
小米举例:“比如你有一个 count 变量,你可以用 volatile 让它的修改对所有线程可见。但如果你要保证 count++ 这个操作的原子性,那 volatile 就不行了,必须用 synchronized。”
2、是否保证原子性
小明皱眉:“等等,那如果 volatile 不能保证原子性,它的用武之地在哪?”
小米解释:“volatile 适用于单一变量的状态标记,比如双重检查锁(DCL)模式下的 instance 变量,或者是 boolean flag 这样的简单开关变量。而 synchronized 适用于复杂逻辑操作,比如 count++ 这种需要原子性保护的操作。”
synchronized 和 volatile 的其他区别
小米继续深入讲解:
1、是否会造成线程阻塞
图片
2、是否会被编译器优化
图片
小明惊讶:“原来 Java 还这么智能,synchronized 还能被优化?”
小米点头:“对的!Java 1.6 以后,synchronized 进行了很多优化,比如:
- 偏向锁:如果一个线程一直在使用同一个锁,JVM 就不会频繁地加锁和释放锁。
- 轻量级锁:多个线程尝试竞争锁时,不会立即进入阻塞状态,而是使用 CAS 方式尝试加锁,提高性能。
- 锁消除、锁膨胀:JVM 会根据实际情况优化锁的使用。”
面试中如何回答 synchronized 和 volatile 的区别
小米总结了一套“黄金答题模板”:
“如果面试官问 synchronized 和 volatile 的区别,你可以这么答:
- volatile只能修饰变量,synchronized 既能修饰变量,也能修饰方法和代码块。
- volatile保证变量的可见性,但不保证原子性;synchronized同时保证可见性和原子性。
- volatile不会造成线程阻塞,而 synchronized 可能会导致线程阻塞。
- volatile不能被编译器优化,而 synchronized 通过 JVM 的优化(如偏向锁、轻量级锁)能提高性能。
- volatile 适用于状态标记等简单场景,而 synchronized 适用于临界区保护、多个操作组合的场景。”
小明眼睛一亮:“听你这么一说,我感觉能答出个八九不离十了!”
真实场景下的选择
小米最后补充道:“不过,面试官可能还会问你——在真实项目中该怎么选? 你可以告诉他:”
- 如果只是想让变量的修改对所有线程可见,且不涉及复合操作(如count++),可以用 volatile。
- 如果需要保证线程安全,操作需要原子性,那就用 synchronized(或者 Lock 机制)。
- 如果涉及高并发,还可以考虑 ReentrantLock,它比 synchronized 更灵活。
面试官的 “坑” 及应对
小米最后提醒:“面试官喜欢挖坑,比如问你:”
1、volatile 适用于哪些场景?
- 适用于状态标记(比如 boolean flag),单例模式的双重检查锁(DCL)。
2、为什么 volatile 不能保证原子性?
- 因为 volatile 只是保证了线程可见性,但 count++ 这样的操作是 读取-计算-写入,中间有多个步骤,volatile 无法保证其不被其他线程干扰。
3、为什么 synchronized 能保证原子性?
- 因为 synchronized 会让线程独占锁,保证操作的完整性,其他线程必须等待锁释放。