news 2026/4/18 12:45:38

多线程编程的代价

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多线程编程的代价

目录

1、竞态条件

1.1、问题本质

1.2、解决方案

2、死锁

2.1、死锁四要素

2.2、Java 死锁

2.3、如何检测死锁

2.4、预防策略

3、性能开销

3.1、锁竞争

3.2、可维护性

4、现代替代方案


前沿

"编写正确的并发程序,比登天还难。"

当一个线程运行时候的整个生命周期,如下所示:

在多核 CPU 成为主流的今天,Java 多线程(Multithreading)被视为提升系统吞吐量和响应速度的“银弹”。然而,并发不是功能,而是复杂性的放大器

设计更复杂

虽然有一些多线程应用程序比单线程的应用程序要简单,但其他的一般都更复杂。在多线程访问共享数据的时候,这部分代码需要特别的注意,线程之间的交互往往非常复杂。

如下所示:

上下文切换的开销

当CPU从执行一个线程切换到执行另外一个线程的时候,它需要先存储当前线程的本地的数据,程序指针等,然后载入另一个线程的本地数据,程序指针等,最后才开始执行。这种切换称为“上下文切换”(“context switch”)。CPU会在一个上下文中执行一个线程,然后切换到另外一个上下文中执行另外一个线程。

上下文切换并不廉价。如果没有必要,应该减少上下文切换的发生。

本文将结合真实代码、底层原理与前沿实践,系统性地揭示 Java 多线程编程的六大核心缺点,并探讨如何在享受并发红利的同时规避其陷阱。


1、竞态条件

1.1、问题本质

当多个线程无序访问共享可变状态,且至少有一个线程在修改数据时,最终结果依赖于线程调度的时序,导致不可预测的行为

Java 代码示例:

public class UnsafeCounter { private int count = 0; public void increment() { count++; // 非原子操作! } public int getCount() { return count; } } // 测试代码 public static void main(String[] args) throws InterruptedException { UnsafeCounter counter = new UnsafeCounter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 100_000; i++) counter.increment(); }); Thread t2 = new Thread(() -> { for (int i = 0; i < 100_000; i++) counter.increment(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Expected: 200000, Actual: " + counter.getCount()); // 输出可能为:138456, 199872, ... 永远不等于 200000 }

为什么 count++不安全?

JVM 字节码层面,count++ 被分解为三步:

// 字节码(简化) getfield count // 读取 iconst_1 // 加1 putfield count // 写回

若两个线程同时执行到 getfield,它们会读取相同的值,导致更新丢失

1.2、解决方案

使用 synchronized:

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

使用 AtomicInteger(无锁 CAS):

private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); }

2、死锁

2.1、死锁四要素

如下所示:

1.互斥(Mutual Exclusion)

线程对所分配到的资源进行排他性使用,即在一段时间内某资源只由一个线程占用。如果此时还有其它线程请求该资源,则请求者只能等待,直至占有资源的线程用毕释放。

2.持有并等待(Hold and Wait)

线程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其它线程占有,此时请求线程阻塞,但又对自己已获得的其它资源保持不放

3.不可抢占(No Preemption)

线程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放

4.循环等待(Circular Wait)

在发生死锁时,必然存在一个线程 —— 资源的环形链,即线程集合 {T0,T1,T2,・・・,Tn} 中的 T0 正在等待一个 T1 占用的资源;T1 正在等待 T2 占用的资源,……,Tn 正在等待已被 T0 占用的资源。

更多关于死锁的知识,可参考:有关Java死锁和活锁的联系

2.2、Java 死锁

示例

public class DeadlockDemo { private final Object lockA = new Object(); private final Object lockB = new Object(); public void methodA() { synchronized (lockA) { System.out.println("Thread " + Thread.currentThread().getName() + " got lockA"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lockB) { // 等待 lockB System.out.println("Acquired both locks"); } } } public void methodB() { synchronized (lockB) { System.out.println("Thread " + Thread.currentThread().getName() + " got lockB"); try { Thread.sleep(100); } catch (InterruptedException e) {} synchronized (lockA) { // 等待 lockA System.out.println("Acquired both locks"); } } } public static void main(String[] args) { DeadlockDemo demo = new DeadlockDemo(); new Thread(demo::methodA, "T1").start(); new Thread(demo::methodB, "T2").start(); // 程序将永久挂起! } }

2.3、如何检测死锁

  • jstack:jstack <pid> 会自动检测死锁并打印:
Found one Java-level deadlock: ============================= "T2": waiting to lock <0x000000076b8a1234> (lockA) "T1": waiting to lock <0x000000076b8a5678> (lockB)

2.4、预防策略

  • 锁排序:所有线程按固定顺序获取锁(如先 A 后 B)

  • 超时机制:tryLock(timeout)

  • 避免嵌套锁


3、性能开销

要知道并发≠加速,有时更慢。

3.1、锁竞争

高并发下,线程频繁争抢同一把锁,导致:

  • 上下文切换开销(用户态 ↔ 内核态)

  • CPU 缓存失效(False Sharing)

False Sharing 示例

public class FalseSharing implements Runnable { public final static int NUM_THREADS = 4; public final static long ITERATIONS = 500L * 1000L * 1000L; private final int arrayIndex; private static VolatileLong[] longs = new VolatileLong[NUM_THREADS]; static { for (int i = 0; i < longs.length; i++) { longs[i] = new VolatileLong(); } } public FalseSharing(final int arrayIndex) { this.arrayIndex = arrayIndex; } public void run() { long i = ITERATIONS + 1; while (0 != --i) { longs[arrayIndex].value = i; // 多个线程写相邻内存 } } public final static class VolatileLong { public volatile long value = 0L; // 在 Java 8+ 可加 @Contended 注解避免 False Sharing } }

若 VolatileLong 对象位于同一 CPU 缓存行(64 字节),一个核心修改会使其他核心缓存失效,性能下降 10 倍以上。

2. 内存消耗

  • 每个 Java 线程默认栈大小1MB(64位 JVM)

  • 1000 个线程 ≈ 1GB 内存仅用于栈

3. 上下文切换成本

  • 切换一次约1~10 微秒

  • 线程数 > CPU 核心数时,吞吐量反而下降

在 16 核机器上,当线程数超过 32,吞吐量开始下降。

如下所示:

3.2、可维护性

1. 逻辑碎片化

业务逻辑被 synchronized、wait/notify、Lock 切割得支离破碎。

2. 异常处理陷阱

synchronized (lock) { try { // 业务逻辑 } finally { // 必须释放锁!否则死锁 lock.unlock(); // ReentrantLock 需手动释放 } }

3. 中断处理困难

  • 如何安全停止一个运行中的线程?

  • Thread.stop() 已废弃(不安全)

  • 正确做法:使用协作式中断(Thread.interrupt() + 检查 isInterrupted())


4、现代替代方案

超越传统多线程,面对多线程的诸多缺点,Java 社区正转向更安全的并发模型:

Java 21 虚拟线程示例(告别线程池)

try (var executor = Executors.newVirtualThreadPerTaskExecutor()) { IntStream.range(0, 10_000).forEach(i -> { executor.submit(() -> { // 每个任务一个虚拟线程,几乎无开销 return doWork(i); }); }); } // 自动 join 所有虚拟线程

虚拟线程不解决竞态条件,但极大降低线程管理成本。


总结

缺点根本原因应对策略
竞态条件共享可变状态使用不可变对象、原子类、同步块
死锁循环等待锁锁排序、超时、避免嵌套
性能开销锁竞争、上下文切换分拆大事务、减少共享、用无锁结构
调试困难非确定性使用 TSan、增加日志、单元测试覆盖
可维护性差逻辑分散封装并发原语、使用高级工具类
资源消耗大线程栈开销采用虚拟线程、协程、异步模型
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 22:19:02

7个实用技巧,通过YashanDB实现数据结构优化

在现代数据库系统中&#xff0c;性能瓶颈和数据一致性问题是常见且挑战较大的技术难题。随着业务数据量和复杂度的快速增长&#xff0c;合理优化数据结构成为提升系统整体性能和稳定性的关键途径。YashanDB作为一款支持单机、分布式和共享集群多种部署形态的高性能数据库&#…

作者头像 李华
网站建设 2026/4/2 17:02:44

基于flask的电影信息网站的设计与实现

目录摘要关于博主开发技术路线相关技术介绍核心代码参考示例结论源码lw获取/同行可拿货,招校园代理 &#xff1a;文章底部获取博主联系方式&#xff01;摘要 随着互联网技术的快速发展&#xff0c;电影信息网站成为用户获取影视资讯的重要渠道。基于Flask框架的电影信息网站设…

作者头像 李华
网站建设 2026/4/16 12:20:04

计算机Java毕设实战-基于springboot的大学生在线考试平台【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/4/16 17:23:53

OPPO AI团队发布O-Mem:让AI助手拥有真正的“记忆“

这项由OPPO AI Agent团队发表于2025年11月的研究论文&#xff0c;首次提出了名为O-Mem的全新记忆框架系统。有兴趣深入了解的读者可以通过arXiv:2511.13593v2查询完整论文。这个系统的突破性在于让AI助手能够像人类一样&#xff0c;通过持续的互动来建立对用户的深度理解&#…

作者头像 李华
网站建设 2026/3/28 23:29:08

计算机Java毕设实战-基于JAVA的旅游管理系统基于SpringBoot+Vue的旅游管理系统【完整源码+LW+部署说明+演示视频,全bao一条龙等】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华