news 2026/1/15 22:16:32

多线程编程的代价

作者头像

张小明

前端开发工程师

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/1/15 22:12:48

企业数字化转型秘籍大揭秘,AI应用架构师的AI方案详细拆解

企业数字化转型秘籍大揭秘&#xff1a;AI应用架构师的AI方案详细拆解 1. 引入与连接&#xff1a;当“老企业”遇到“新问题” 1.1 一个真实的痛点故事 某传统制造企业的车间主任老张最近很头疼&#xff1a; 生产线经常因为设备故障停摆&#xff0c;每次维修要等2小时&#xff0…

作者头像 李华
网站建设 2026/1/15 22:11:22

7个实用技巧提升YashanDB数据库的安全性

在当前的数据驱动时代&#xff0c;数据库的安全性至关重要。数据库常常是攻击的目标&#xff0c;而对数据的保护不仅关乎企业的商业利益&#xff0c;还涉及遵循法律和合规要求。因此&#xff0c;确立强有力的安全措施以保护数据库的完整性和机密性是每个DBA和企业必须关注的重点…

作者头像 李华
网站建设 2026/1/15 22:09:04

如何设计一个网关

如何设计一个网关:从概念到实践的全方位指南 1. 引入与连接:网关的核心价值与定位 1.1 故事引入:企业面临的网络挑战 想象一下,你是一家快速发展的电商企业的技术负责人。几年前,你的系统还是一个单体应用,所有功能都集中在一个代码库中,部署在几台服务器上。那时,用…

作者头像 李华
网站建设 2026/1/15 22:04:42

手把手教你用6款免费AI论文神器:选题到降重一站式搞定

你是不是正在为论文而焦虑&#xff1f;从选题的迷茫&#xff0c;到文献的浩如烟海&#xff0c;再到导师那句“再改改”的恐惧&#xff0c;每一步都让人头大。别担心&#xff0c;你的“AI学术搭子”已经就位&#xff01; 作为一名曾经的“论文困难户”&#xff0c;我深知其中的…

作者头像 李华
网站建设 2026/1/15 22:04:35

全网最全自考必备TOP10 AI论文平台测评

全网最全自考必备TOP10 AI论文平台测评 2026年自考AI论文平台测评&#xff1a;为什么需要这份榜单&#xff1f; 随着人工智能技术的不断进步&#xff0c;越来越多的自考学生开始依赖AI写作工具来提升论文撰写效率。然而&#xff0c;面对市场上琳琅满目的平台&#xff0c;如何选…

作者头像 李华
网站建设 2026/1/15 22:02:40

新加坡国立大学团队让机器人拥有“时空眼“

这项由新加坡国立大学计算学院的周翰宇和李锦熙教授&#xff0c;以及华中科技大学人工智能与自动化学院的马传昊共同完成的研究&#xff0c;发表于2025年11月21日的arXiv预印本平台&#xff0c;论文编号为arXiv:2511.17199v1。有兴趣深入了解的读者可以通过这个编号查询完整论文…

作者头像 李华