Java并发编程中常见误区盘点:别再用错synchronized 和 volatile

 更新时间:2026年06月10日 08:36:56   作者:小强1988  
这篇文章主要为大家详细介绍了Java并发编程中常见的误区盘点,主要是synchronized和volatile这两个Java并发编程中的常见的关键字,感兴趣的小伙伴可以了解下

很多人觉得自己会用 synchronized 和 volatile,直到线上炸了才发现——根本没用对。

一、synchronized:你以为你锁住了,其实什么都没锁住

误区1:把原子操作拆开,分开加锁 = 没加锁

这是最经典的"自以为是"型错误。

// ❌ 错误示范:count++ 被拆成三段,中间的无锁区间让并发问题卷土重来
public void wrongAdd() {
    synchronized(this) { count = count; }  // 第一步:读
    int temp = count + 1;                   // ⚠️ 无锁区间!其他线程可以修改 count
    synchronized(this) { count = temp; }   // 第二步:写
}

两个线程各执行10000次,结果远小于20000。原因很简单:读和写之间的无锁窗口,就是并发bug的温床。

// ✅ 正确做法:完整操作包裹在同一把锁内
public synchronized void correctAdd() {
    count++;  // 读→计算→写,三步原子完成,无中间间隙
}

原则:需要保证原子性的完整操作,必须全部包裹在同一把锁内,操作没完成不释放锁。

误区2:非静态同步方法只锁"当前方法"?错,锁的是整个实例

synchronized void method() 的锁是 this,而不是某个方法名。这意味着:

对同一个实例,只要有一个非静态同步方法被锁住,该实例的所有非静态同步方法都会被阻塞——锁住一个,锁住全部。

public synchronized void m1() { /* 占用实例锁 */ }
public synchronized void m2() { /* 必须等待 m1 释放锁 */ }
public void m3() { /* 不受影响,立即执行 */ }

实验结果:线程1进入m1后,线程2调用m2被阻塞,但线程3调用m3畅通无阻。

不同实例之间完全互不影响——每个实例有独立的锁。

误区3:静态同步方法和非静态同步方法用同一把锁?完全独立

这是重灾区。

类型锁对象说明
非静态 synchronized 方法当前实例 this实例锁
静态 synchronized 方法类的 Class 对象 MyClass.class类锁

两者完全独立,互不干扰。  一个线程锁住静态方法,另一个线程照样可以执行非静态方法。

public static synchronized void staticM1() { /* 锁的是 MyClass.class */ }
public synchronized void instanceM() { /* 锁的是 this */ }

这两把锁就像两条平行的高速公路,永远不会堵车到一起。

误区4:synchronized(lock) 和 lock.lock() 混合使用

有人试图"双保险",结果两把锁根本不是同一把:

private final Lock lock = new ReentrantLock();
public void m1() {
    synchronized(lock) { /* 锁的是 lock 对象本身 */ }
}
public void m2() {
    lock.lock();        /* 锁的是 ReentrantLock 内部的 AQS */
}

synchronized 把 lock 实例当作锁标志,而 ReentrantLock 的 lock() 方法锁的是其他东西。  两套机制各行其是,互不买账,所谓的"双保险"形同虚设。

误区5:用 String、Integer、Boolean 做锁对象

// ❌ 字符串字面量在常量池中,所有代码共享同一个对象
synchronized("LOCK") { ... }
// ❌ Integer(-128~127) 有缓存池
synchronized(Integer.valueOf(1)) { ... }
// ❌ Boolean.TRUE 全局唯一
synchronized(Boolean.TRUE) { ... }

这些对象可能被JVM其他部分、甚至不受信任的外部代码共用。一旦别的代码也在同一把锁上等着,死锁就来了

正确做法:用 private final Object lock = new Object(),私有、唯一、可控。

二、volatile:你以为它万能,其实它啥也保不住

核心认知:volatile 只保两样东西

特性volatilesynchronized
可见性✅ 保证✅ 保证
有序性(禁止重排序)✅ 保证✅ 保证(线程间)
原子性❌ 不保证✅ 保证
互斥性❌ 不保证✅ 保证

volatile 是一把精准的"可见性扳手",而非万能的"线程安全锤子"。

误区1:volatile 能保证 count++ 线程安全

这是被坑得最多的场景。

private volatile int count = 0;
public void increment() {
    count++;  // 读→改→写,三步操作
}

volatile 只能保证每次读到的是最新值,但它拦不住其他线程插在中间执行。两个线程同时读到1,各自加成2,写回还是2——丢了一次更新。

正确方案:AtomicInteger.incrementAndGet(),或用 synchronized 包裹。

误区2:volatile 能替代锁

private volatile List<Listener> listeners = new CopyOnWriteArrayList<>();

虽然 listeners 是 volatile 的,但对 listeners 的复合操作并不线程安全。volatile 保的是引用本身的可见性,不保引用指向的对象内部操作的原子性。

误区3:所有共享变量都加 volatile 就安全了

有人为了"省事"把共享变量全标上 volatile。但 volatile 有性能开销(需插入内存屏障),而且无法保证多个 volatile 变量之间的操作原子性

volatile int state1;
volatile int state2;
// 多线程下 state1 和 state2 仍可能出现不一致
if (state1 == 1 && state2 == 0) { ... }

三、synchronized 的有序性也有陷阱

很多人以为 synchronized 能完全禁止指令重排序。真相是:它只保证线程间的操作顺序,同步块内部的指令仍可能被重排序(只要不影响单线程执行结果)。

而 volatile 才是真正通过内存屏障禁止与 volatile 变量相关的指令重排序的机制。这也是 DCL 单例模式中必须加 volatile 的原因——防止对象初始化被重排序为"分配内存→返回引用→执行构造函数",导致其他线程拿到一个半成品对象。

四、终极对比:什么时候用什么

场景推荐方案理由
状态标志位(running/stop)volatile boolean轻量,可见性足够
计数器、累加器AtomicInteger原子性 + 可见性
复杂临界区(转账等)synchronized / ReentrantLock需要互斥 + 原子性
单例 DCLvolatile + synchronizedvolatile 防重排序,synchronized 保原子性
线程池ThreadPoolExecutor(禁止用 Executors.newFixedThreadPool()前者可控队列大小,后者用无界队列,高负载直接 OOM

写在最后

并发编程的坑,从来不在 API 本身,而在你对它的理解偏差

  • synchronized 锁的是对象,不是方法名
  • volatile 保的是可见性,不是原子性
  • 拆开加锁 = 没锁,混合加锁 = 乱锁
  • 别拿 String 和 Integer 当锁,那是在给死锁递刀子

真正难的不是记住这些规则,而是上线前能不能想到: "我这个 volatile 变量,是不是真的被所有线程看到了?我这个 synchronized,锁的到底是哪个对象?"

往往出问题的,都是那些看起来"应该没问题"的地方。

以上就是Java并发编程中常见误区盘点:别再用错synchronized 和 volatile的详细内容,更多关于Java并发编程常见误区的资料请关注脚本之家其它相关文章!

相关文章

  • jdk17 SpringBoot JPA集成多数据库的示例详解

    jdk17 SpringBoot JPA集成多数据库的示例详解

    这篇文章主要介绍了jdk17 SpringBoot JPA集成多数据库的示例代码,包括配置类、请求拦截器、线程上下文等相关知识,代码简单易懂,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-08-08
  • 详解java_ 集合综合案例:斗地主

    详解java_ 集合综合案例:斗地主

    这篇文章主要介绍了java_ 集合综合案例:斗地主,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • 详解Java编程中线程同步以及定时启动线程的方法

    详解Java编程中线程同步以及定时启动线程的方法

    这篇文章主要介绍了详解Java编程中线程同步以及定时启动线程的方法, 讲到了wait()与notify()方法以及阻塞队列等知识,需要的朋友可以参考下
    2016-01-01
  • 长度最小的子数组题目详解(Java版)

    长度最小的子数组题目详解(Java版)

    这篇文章主要给大家介绍了关于长度最小的子数组(Java版)的相关资料,这到题来自力扣,通过学习本文对大家理解这道题目有很大的帮助,需要的朋友可以参考下
    2023-12-12
  • Java泛型和Class类用法示例

    Java泛型和Class类用法示例

    这篇文章主要介绍了Java泛型和Class类用法,结合实例形式分析了java使用泛型限制class类避免强制类型转换相关操作技巧,需要的朋友可以参考下
    2019-07-07
  • Java编程实现swing圆形按钮实例代码

    Java编程实现swing圆形按钮实例代码

    这篇文章主要介绍了Java编程实现swing圆形按钮实例代码,涉及两个简单的Java实现按钮的代码,其中一个具有侦测点击事件的简单功能,具有一定借鉴价值,需要的朋友可以参考。
    2017-11-11
  • javax.net.ssl.SSLHandshakeException:异常原因及解决方案

    javax.net.ssl.SSLHandshakeException:异常原因及解决方案

    javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SSL连接时发生,这篇文章主要介绍了javax.net.ssl.SSLHandshakeException:异常原因及解决方案,需要的朋友可以参考下
    2025-06-06
  • 编程入门:掌握Java运算符技巧

    编程入门:掌握Java运算符技巧

    掌握Java运算符技巧,能让你的编程之旅轻松许多,本指南将带你深入了解如何巧妙地使用这些强大的工具,让代码不仅高效,还充满乐趣,跟着我们一起,让你的Java代码在运算符的魔法下焕发新生!
    2023-12-12
  • SpringBoot项目后端开发逻辑全面梳理

    SpringBoot项目后端开发逻辑全面梳理

    这篇文章主要介绍了SpringBoot项目后端开发逻辑全面梳理,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-11-11
  • Java 链表的定义与简单实例

    Java 链表的定义与简单实例

    这篇文章主要介绍了 Java 链表的定义与简单实例的相关资料,需要的朋友可以参考下
    2017-06-06

最新评论