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 只解决两个问题:

可见性

禁止指令重排序

不保证原子性

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

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

count++ 实际是三步:

  1. 读取 count

  2. count + 1

  3. 写回 count

多线程下必然出问题。


2️⃣ volatile 的正确使用姿势

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

    public void stop() {
        running = false;
    }

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

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

✔ 无复合操作

✔ 完美匹配 volatile


✅ 场景二:双重检查锁定(DCL)
复制代码
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 的最佳实践

复制代码
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 的核心优势

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

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

✅ 可中断(lockInterruptibly

✅ 超时获取锁(tryLock(timeout)

✅ 公平锁

✅ 多条件变量(Condition


2️⃣ Lock 的典型使用场景

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

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


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

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


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

忘记 unlock

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

✅ 必须放在 finally


重复加锁导致死锁

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

性能反而更差

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


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

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

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

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

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

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

复制代码
private volatile int stock = 100;

public void reduceStock() {
    stock--;
}

结果:

✈️ 库存扣成负数

💸 资损事故

原因:volatile 不保证原子性

✅ 正确做法:

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

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

八、终极建议

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

  • 能用不可变对象就不用 volatile

  • 能用 synchronized 就不用 Lock

  • 能用并发容器就不用手写锁

  • 能用 AtomicXXX 就不用 synchronized


九、一句话总结

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

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

相关推荐
魏码不凡2 小时前
CURL报错:未找到SSL证书文件问题
开发语言·php·ssl
geovindu2 小时前
go:loghelper
开发语言·后端·golang
IMPYLH2 小时前
HTML 的 <em> 元素
java·前端·html
啊真真真3 小时前
ArgoCD:我的GitOps探索之旅与未来展望
java·算法·argocd
深入云栈3 小时前
从零手写连接池:Redisson ConnectionsHolder设计与简化实现
java·架构
学编程就要猛3 小时前
解析博客系统后端实现
java·mysql·jwt·摘要算法·加盐
笨蛋不要掉眼泪3 小时前
RabbitMQ消息队列:交换机机制
java·分布式·rabbitmq
听雨入夜3 小时前
zero.zhang
开发语言·python
江屿风3 小时前
【C++笔记】【二叉搜索树】流食般投喂
开发语言·数据结构·c++·笔记