Home 并发编程:为什么并发编程容易出问题
Post
Cancel

并发编程:为什么并发编程容易出问题

Java 并发编程 中,容易出问题的根本原因主要来源于 Java 的内存模型(JMM)线程调度机制锁机制


Java 并发编程容易出问题的根本原因


1. Java 内存模型(JMM)导致的“可见性问题”

原因:

  • Java 中每个线程有自己的工作内存(缓存),和主内存之间同步是延迟的。
  • 一个线程修改变量后,另一个线程可能看不到最新值

解决方式:

  • 使用 volatile 关键字保证变量的可见性
  • 使用锁(synchronized、ReentrantLock)隐式刷新工作内存

2. 原子性问题:多个操作不可分割

原因:

  • Java 中复合操作(如 i++list.add())不是原子操作,可能被线程中断。

解决方式:

  • 使用 synchronizedLock 保证原子性
  • 使用 AtomicIntegerConcurrentHashMap 等原子类

3. 有序性问题:指令重排序

原因:

  • Java 编译器和 CPU 会对指令进行重排序优化,在单线程无影响,但在多线程下可能出现意料之外的顺序。

解决方式:

  • 使用 volatile 禁止重排序
  • 使用锁等同步机制建立“happens-before 关系

4. 线程调度不可控(执行顺序不确定)

原因:

  • Java 线程调度由 JVM 和操作系统共同决定,线程启动顺序/运行时间是不可预测的
  • 引发竞态条件(race condition)

解决方式:

  • 尽可能使用显式同步机制来控制线程顺序
  • 使用 CountDownLatchSemaphoreCyclicBarrier 等并发工具

5. 锁机制复杂易出错

原因:

  • 锁使用不当可能导致:

    • 死锁:两个线程相互等待锁资源
    • 活锁:线程不断重试却无法前进
    • 饥饿:低优先级线程长期得不到执行机会

解决方式:

  • 避免嵌套锁
  • 使用 tryLock 设置超时
  • 使用高级并发工具类替代手动加锁

6. Bug 难复现、难调试、难测试

原因:

  • 并发 bug 多为非确定性问题,并非每次都出现
  • 即使加了日志,也可能由于线程切换导致“日志错位”
  • 单元测试覆盖不到所有并发场景

解决方式:

  • 使用工具:如 Java Flight Recorder、VisualVM、JMH、FindBugs、jstack 分析
  • 使用并发测试工具如 JCStress

7. 开发者模型不匹配(人脑不擅长思考并发)

原因:

  • 人脑更擅长线性思维,不擅长在脑中模拟多个线程并行执行的状态组合。
  • 并发引发的问题往往涉及“某种罕见的交叉时间点”,很难人眼推演。

总结一句话:

Java 并发问题的本质,是在“多个线程共享变量”+“非原子操作”+“不可见内存”+“不确定调度” 的多重组合下,引发状态错乱和 bug。


weixin.png

公众号名称:怪味Coding
微信扫码关注或搜索公众号名称
This post is licensed under CC BY 4.0 by the author.

并发编程:Java 线程生命周期

并发编程:可重入锁、不可重入锁、公平锁、非公平锁