Java 并发大坑:volatile、synchronized、Lock 三者如何选择?

Java 并发大坑:volatile、synchronized、Lock 三者如何选择?

很多 Java 程序员对 volatilesynchronizedLock 的理解停留在"背八股"层面:

volatile 保证可见性,synchronized 重量级,Lock 更灵活。

但真正写并发代码时,选错一个,线上就是事故 。本文从原理 → 适用场景 → 典型坑点 → 选择决策表,一次性讲清楚三者的正确打开方式。


一、先给结论(速查版)

场景 推荐
单纯状态标志(如停止线程) volatile
复合操作(i++、check-then-act) volatile / ✅ synchronized / ✅ Lock
单线程写、多线程读 volatile
临界区保护、简单互斥 synchronized(首选)
需要公平锁、可中断、超时、多条件队列 Lock(ReentrantLock)
高并发 + 低竞争 synchronized(JVM 优化好)
高并发 + 高竞争 + 复杂控制 Lock

👉 一句话原则

能用 volatile 就别用锁;能用 synchronized 就别用 Lock


二、volatile:最容易被误用的关键字

1️⃣ volatile 到底解决了什么?

volatile 只解决两个问题:

可见性

禁止指令重排序

不保证原子性

csharp 复制代码
// 错误示例
private volatile int count = 0;

public void increment() {
    count++; // 非原子操作!
}

count++ 实际是三步:

  1. 读取 count
  2. count + 1
  3. 写回 count

多线程下必然出问题。


2️⃣ volatile 的正确使用姿势

✅ 场景一:状态标志(最常见 & 最正确)
arduino 复制代码
class Worker implements Runnable {
    private volatile boolean running = true;

    public void stop() {
        running = false;
    }

    @Override
    public void run() {
        while (running) {
            // do work
        }
    }
}

✔ 一个线程写,多个线程读

✔ 无复合操作

✔ 完美匹配 volatile


✅ 场景二:双重检查锁定(DCL)
csharp 复制代码
class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

⚠️ 没有 volatile = 可能返回半初始化对象


3️⃣ volatile 的经典误区

❌ 误区 1:volatile 可以替代锁

❌ 误区 2:volatile i++ 是线程安全的

❌ 误区 3:volatile 一定比锁快(在竞争激烈时未必)


三、synchronized:被低估的王者

1️⃣ synchronized 做了什么?

synchronized 保证:

✅ 原子性

✅ 可见性

✅ 有序性(临界区内)

本质:互斥 + 内存屏障


2️⃣ JVM 对 synchronized 的优化(非常重要)

很多人还停留在"synchronized 是重量级锁"的旧认知里。

现代 JVM(JDK 8+)已经实现了锁升级机制:

复制代码
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 低竞争:几乎无开销
  • 高竞争:自动升级,性能可控

👉 在大多数业务系统中,synchronized 性能优于 Lock


3️⃣ synchronized 的最佳实践

arduino 复制代码
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

✔ 简单

✔ 安全

✔ 不易出错


4️⃣ synchronized 的局限性

❌ 无法响应中断

❌ 无法尝试获取锁(tryLock)

❌ 无法实现公平锁

❌ 条件队列只有一个(wait/notify)

当你需要这些能力时,才考虑 Lock


四、Lock:功能最强,但最危险

1️⃣ Lock 的核心优势

csharp 复制代码
ReentrantLock lock = new ReentrantLock();

lock.lock();
try {
    // 临界区
} finally {
    lock.unlock();
}

✅ 可中断(lockInterruptibly

✅ 超时获取锁(tryLock(timeout)

✅ 公平锁

✅ 多条件变量(Condition


2️⃣ Lock 的典型使用场景

✅ 场景一:可中断锁
csharp 复制代码
lock.lockInterruptibly();
try {
    // 处理任务
} finally {
    lock.unlock();
}

用于防止死锁、响应线程中断


✅ 场景二:多条件队列
ini 复制代码
Condition notFull = lock.newCondition();
Condition notEmpty = lock.newCondition();

这是 synchronized + wait/notify 做不到的。


3️⃣ Lock 的大坑(90% 的人踩过)

忘记 unlock

scss 复制代码
lock.lock();
doSomething(); // 抛异常 → 锁永远不释放

✅ 必须放在 finally


重复加锁导致死锁

csharp 复制代码
lock.lock();
lock.lock(); // 忘了解锁一次

性能反而更差

在低竞争场景下,Lock 的性能通常 不如 synchronized


五、三者对比总结(面试 & 实战必看)

特性 volatile synchronized Lock
原子性
可见性
有序性 部分
可重入
可中断
公平锁
多条件队列
复杂度 ⭐⭐ ⭐⭐⭐⭐
推荐程度 慎用 ⭐⭐⭐⭐⭐ ⭐⭐⭐

六、选择决策流程图(建议收藏)

arduino 复制代码
是否只是状态标志?
 ├─ 是 → volatile
 └─ 否
    是否需要复杂锁控制(中断/超时/公平/多条件)?
     ├─ 是 → Lock
     └─ 否
         → synchronized

七、真实线上事故案例(警示)

💥 案例:volatile + i++ 导致库存超卖

csharp 复制代码
private volatile int stock = 100;

public void reduceStock() {
    stock--;
}

结果:

✈️ 库存扣成负数

💸 资损事故

原因:volatile 不保证原子性

✅ 正确做法:

java 复制代码
synchronized (this) {
    stock--;
}

ini 复制代码
AtomicInteger stock = new AtomicInteger(100);
stock.decrementAndGet();

八、终极建议

并发编程的第一原则是:不要自己发明并发控制。

  • 能用不可变对象就不用 volatile
  • 能用 synchronized 就不用 Lock
  • 能用并发容器就不用手写锁
  • 能用 AtomicXXX 就不用 synchronized

九、一句话总结

volatile 管"看见",synchronized 管"互斥",Lock 管"精细控制"。

能不用锁就不用锁,能用简单锁就不用复杂锁。

相关推荐
神经蛙199615 小时前
🌍 别再硬编码中文了!Python Web 项目国际化(i18n)完全指南
后端·python
二月龙16 小时前
Spring 事务失效的 8 种场景,很多老手依然频繁踩雷
后端
掘金酱16 小时前
「TRAE Work 实战帮」征文启动!你沉淀的经验,值得被看见!
前端·人工智能·后端
长大198816 小时前
MyBatis 常见性能陷阱:N+1 查询、一级缓存踩坑解决方案
后端
用户18615580086016 小时前
MinIO Java 对接试用:从连接、上传到下载的完整示例
后端
爱勇宝16 小时前
DeepSeek V4-Flash 更新:代码与 Agent 能力全面增强
前端·后端·deepseek
极客悟道16 小时前
SDKMAN vs jEnv vs JetTUI,JDK 版本管理到底选哪个
后端
长大198816 小时前
Java8 新特性到底要不要吃透?工作中高频使用的 5 个功能总结
后端
二月龙16 小时前
Spring Bean 生命周期 & 循环依赖:90% 开发者只知结论不懂原理
后端