volatile vs synchronized:Java 并发两大护法
·
volatile vs synchronized:Java 并发两大"护法"
volatile和 synchronized是 Java 解决并发问题的两个核心关键字,但它们的原理、适用场景和成本完全不同。
核心区别总览
| 特性 | volatile | synchronized |
|---|---|---|
| 本质 | 轻量级,变量可见性保证 | 重量级,原子性+可见性保证 |
| 作用范围 | 只能修饰变量 | 可以修饰方法、代码块 |
| 原子性 | ❌ 不保证(对复合操作无效) | ✅ 保证 |
| 可见性 | ✅ 保证(立即刷新到主内存) | ✅ 保证 |
| 有序性 | ✅ 禁止指令重排序 | ✅ 保证有序性 |
| 阻塞 | ❌ 不会阻塞线程 | ✅ 会阻塞线程(锁) |
| 性能 | 高(几乎无开销) | 低(上下文切换开销) |
| 使用场景 | 状态标志、双重检查锁 | 临界区、资源争用 |
volatile 详解
1. 可见性保证
没有 volatile 的问题:
public class VisibilityProblem {
private static boolean flag = false; // ❌ 没有volatile
public static void main(String[] args) throws InterruptedException {
Thread writer = new Thread(() -> {
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; // 修改标志
System.out.println("Writer: flag 设置为 true");
});
Thread reader = new Thread(() -> {
while (!flag) {
// 可能永远循环!因为看不到flag的变化
}
System.out.println("Reader: 检测到 flag 为 true");
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
// 输出可能只有:Writer: flag 设置为 true
// Reader线程可能永远看不到变化
使用 volatile 解决:
public class VisibilitySolution {
private static volatile boolean flag = false; // ✅ 加上volatile
public static void main(String[] args) throws InterruptedException {
Thread writer = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true; // 修改会立即刷到主内存
System.out.println("Writer: flag 设置为 true");
});
Thread reader = new Thread(() -> {
while (!flag) {
// 现在能看到变化了!
}
System.out.println("Reader: 检测到 flag 为 true");
});
writer.start();
reader.start();
writer.join();
reader.join();
}
}
// 正确输出:
// Writer: flag 设置为 true
// Reader: 检测到 flag 为 true
2. 禁止指令重排序
没有 volatile 的重排序问题:
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
// ❌ 问题:这里可能重排序!
instance = new Singleton();
// 实际执行可能:
// 1. 分配内存
// 2. 引用赋值(instance不为null了)
// 3. 初始化对象
// 其他线程可能拿到未初始化的对象!
}
}
}
return instance;
}
}
使用 volatile 解决:
public class Singleton {
private static volatile Singleton instance; // ✅ 加上volatile
private Singleton() {
// 初始化操作
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton(); // ✅ 不会重排序
}
}
}
return instance;
}
}
3. volatile 的局限性
public class VolatileLimitation {
private volatile int count = 0;
public void increment() {
count++; // ❌ 这不是原子操作!
// 实际是3步:
// 1. 读取count
// 2. count + 1
// 3. 写回count
// 多线程会丢失更新
}
public static void main(String[] args) throws InterruptedException {
VolatileLimitation demo = new VolatileLimitation();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终结果: " + demo.count);
// 可能输出:15678(而不是20000)
}
}
synchronized 详解
1. 基本用法
public class SynchronizedDemo {
private int count = 0;
private final Object lock = new Object();
// 1. 实例方法同步
public synchronized void increment1() {
count++;
}
// 2. 静态方法同步
public static synchronized void staticMethod() {
// 锁是 SynchronizedDemo.class
}
// 3. 同步代码块(指定锁对象)
public void increment2() {
synchronized (lock) { // 使用特定对象作为锁
count++;
}
}
// 4. 同步代码块(this锁)
public void increment3() {
synchronized (this) { // 使用当前实例作为锁
count++;
}
}
}
2. 保证原子性
public class AtomicityDemo {
private int count = 0;
public synchronized void safeIncrement() {
count++; // ✅ 原子操作
}
public void unsafeIncrement() {
count++; // ❌ 非原子操作
}
public static void main(String[] args) throws InterruptedException {
AtomicityDemo demo = new AtomicityDemo();
// 测试安全版本
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.safeIncrement();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.safeIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("安全版本结果: " + demo.count); // 总是20000
}
}
3. 锁的升级过程
// JDK 1.6后的synchronized优化
public class LockUpgrade {
private Object obj = new Object();
public void method() {
synchronized (obj) { // 锁状态变化:
// 1. 无锁 → 偏向锁(单线程)
// 2. 偏向锁 → 轻量级锁(有竞争但不多)
// 3. 轻量级锁 → 重量级锁(竞争激烈)
// 锁消除、锁粗化等优化
}
}
}
实际场景对比
场景1:状态标志
// ✅ 适合用 volatile
public class ShutdownHook {
private volatile boolean shutdownRequested = false;
public void shutdown() {
shutdownRequested = true; // 立即对所有线程可见
}
public void doWork() {
while (!shutdownRequested) {
// 执行工作
}
System.out.println("接收到关闭信号,优雅退出");
}
}
// ❌ 不适合用 synchronized(太重)
public class ShutdownHookHeavy {
private boolean shutdownRequested = false;
public synchronized void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (true) {
synchronized (this) {
if (shutdownRequested) {
break;
}
}
// 执行工作
}
System.out.println("接收到关闭信号,优雅退出");
}
}
场景2:计数器
// ❌ 不适合用 volatile
public class Counter {
private volatile int count = 0;
public void increment() {
count++; // 仍然线程不安全!
}
public int getCount() {
return count;
}
}
// ✅ 适合用 synchronized
public class SafeCounter {
private int count = 0;
public synchronized void increment() {
count++; // 线程安全
}
public synchronized int getCount() {
return count;
}
}
// ✅ 更好的方案:AtomicInteger
import java.util.concurrent.atomic.AtomicInteger;
public class BetterCounter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet(); // CAS操作,无锁
}
public int getCount() {
return count.get();
}
}
场景3:单例模式
// 方案1:synchronized 方法(简单但性能差)
class Singleton1 {
private static Singleton1 instance;
private Singleton1() {}
public static synchronized Singleton1 getInstance() {
if (instance == null) {
instance = new Singleton1();
}
return instance;
}
}
// 方案2:双重检查锁 + volatile
class Singleton2 {
private static volatile Singleton2 instance;
private Singleton2() {}
public static Singleton2 getInstance() {
if (instance == null) {
synchronized (Singleton2.class) {
if (instance == null) {
instance = new Singleton2();
}
}
}
return instance;
}
}
// 方案3:静态内部类(推荐)
class Singleton3 {
private Singleton3() {}
private static class Holder {
static final Singleton3 INSTANCE = new Singleton3();
}
public static Singleton3 getInstance() {
return Holder.INSTANCE;
}
}
性能对比测试
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class PerformanceComparison {
private static final int THREAD_COUNT = 100;
private static final int LOOP_COUNT = 100000;
private volatile int volatileCount = 0;
private int synchronizedCount = 0;
private AtomicInteger atomicCount = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
PerformanceComparison demo = new PerformanceComparison();
System.out.println("=== volatile 测试 ===");
long time1 = demo.testVolatile();
System.out.println("volatile 最终值: " + demo.volatileCount);
System.out.println("\n=== synchronized 测试 ===");
long time2 = demo.testSynchronized();
System.out.println("synchronized 最终值: " + demo.synchronizedCount);
System.out.println("\n=== AtomicInteger 测试 ===");
long time3 = demo.testAtomic();
System.out.println("AtomicInteger 最终值: " + demo.atomicCount.get());
System.out.println("\n=== 性能对比 ===");
System.out.println("volatile 耗时: " + time1 + "ms");
System.out.println("synchronized 耗时: " + time2 + "ms");
System.out.println("AtomicInteger 耗时: " + time3 + "ms");
}
private long testVolatile() throws InterruptedException {
volatileCount = 0;
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
volatileCount++; // ❌ 不安全,但测性能
}
latch.countDown();
}).start();
}
latch.await();
return System.currentTimeMillis() - start;
}
private long testSynchronized() throws InterruptedException {
synchronizedCount = 0;
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
incrementSync();
}
latch.countDown();
}).start();
}
latch.await();
return System.currentTimeMillis() - start;
}
private synchronized void incrementSync() {
synchronizedCount++;
}
private long testAtomic() throws InterruptedException {
atomicCount.set(0);
CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
long start = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
for (int j = 0; j < LOOP_COUNT; j++) {
atomicCount.incrementAndGet();
}
latch.countDown();
}).start();
}
latch.await();
return System.currentTimeMillis() - start;
}
}
内存模型与 happens-before
volatile 的 happens-before
public class HappensBefore {
private int x = 0;
private volatile boolean v = false;
public void writer() {
x = 42; // 1
v = true; // 2
}
public void reader() {
if (v) { // 3
// 这里一定能看到 x == 42
// 因为 1 happens-before 2
// 2 happens-before 3(volatile写-读规则)
// 所以 1 happens-before 3
System.out.println(x); // 输出42
}
}
}
synchronized 的 happens-before
public class SynchronizedHappensBefore {
private int x = 0;
public synchronized void writer() {
x = 42; // 1
} // 2 解锁
public synchronized void reader() {
// 3 加锁
// 这里一定能看到 x == 42
// 因为 1 happens-before 2(程序顺序)
// 2 happens-before 3(监视器锁规则)
System.out.println(x); // 输出42
} // 4 解锁
}
常见错误
错误1:误用 volatile
// ❌ 错误:以为 volatile 能保证原子性
public class WrongVolatile {
private volatile int a = 0;
private volatile int b = 0;
public void set() {
a = 1; // 操作1
b = 2; // 操作2
// 其他线程可能看到 b=2 但 a!=1
// volatile 不保证多个变量的原子性
}
}
错误2:过度同步
// ❌ 错误:不需要同步的地方用了同步
public class OverSynchronized {
private final List<String> list = Collections.synchronizedList(new ArrayList<>());
public void addIfAbsent(String item) {
synchronized (list) { // ❌ 多余的同步
if (!list.contains(item)) {
list.add(item);
}
}
// Collections.synchronizedList 已经同步了
}
}
错误3:锁对象不一致
// ❌ 错误:使用不同的锁对象
public class WrongLock {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private int count = 0;
public void increment() {
synchronized (lock1) { // 用lock1
count++;
}
}
public int get() {
synchronized (lock2) { // ❌ 用lock2,无法保护count
return count;
}
}
}
最佳实践
1. 选择原则
// 如何选择 volatile 还是 synchronized?
if (满足以下所有条件) {
return "用 volatile";
// 1. 对变量的写操作不依赖当前值
// 2. 该变量没有包含在具有其他变量的不变式中
// 3. 访问变量时不需要加锁
} else {
return "用 synchronized 或其他锁机制";
}
2. 现代替代方案
// 1. 用 Atomic 类代替 volatile
private volatile int count; // 旧方式
private AtomicInteger count = new AtomicInteger(); // 新方式
// 2. 用 ReentrantLock 代替 synchronized
private final ReentrantLock lock = new ReentrantLock();
public void method() {
lock.lock();
try {
// 临界区
} finally {
lock.unlock();
}
}
// 3. 用 StampedLock 优化读多写少
private final StampedLock lock = new StampedLock();
public void read() {
long stamp = lock.tryOptimisticRead();
// 读操作...
if (!lock.validate(stamp)) {
stamp = lock.readLock();
try {
// 重新读
} finally {
lock.unlockRead(stamp);
}
}
}
3. 性能优化技巧
// 1. 缩小同步范围
public void slowMethod() {
// 很多不需要同步的代码...
synchronized (this) {
// 只有这一小段需要同步
}
// 更多不需要同步的代码...
}
// 2. 使用读写锁分离
public class ReadWriteCache {
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Map<String, Object> cache = new HashMap<>();
public Object get(String key) {
lock.readLock().lock();
try {
return cache.get(key);
} finally {
lock.readLock().unlock();
}
}
public void put(String key, Object value) {
lock.writeLock().lock();
try {
cache.put(key, value);
} finally {
lock.writeLock().unlock();
}
}
}
总结:
volatile 是"轻量级同步":
- 只保证可见性和禁止重排序
- 不保证原子性
- 适合状态标志、双重检查锁
synchronized 是"重量级同步":
- 保证原子性、可见性、有序性
- 会阻塞线程
- 适合临界区保护
选择指南:
- 如果只是一个线程写,多个线程读的状态标志 →
volatile - 如果需要复合操作的原子性 →
synchronized - 如果竞争不激烈 → 考虑
synchronized优化后的轻量级锁 - 如果读多写少 → 考虑
ReadWriteLock - 如果只是计数器 → 用
Atomic类
记住核心:volatile解决可见性问题,synchronized解决原子性问题。理解它们的差异,才能写出正确高效的多线程代码。
更多推荐


所有评论(0)