当前位置: 首页 > news >正文

Java多线程常见误区与最佳实践总结

1. 没有做线程管理

问题现象:相信很多人第一次接触多线程时,都是这样学的:
创建一个 Thread 对象,然后调用 start() 就可以了,于是写出这样一段经典的代码:

Thread t = new Thread(() -> {System.out.println("启动一个线程...");
});
t.start();

这段代码表面上没问题,但在复杂系统中,这种做法会埋下隐患。
深层原因:不要误以为线程和函数类似,调用它们就会并行执行。使用线程也就意味着你需要自己管理以下几个方面

  • 线程的生命周期
  • 内存可见性与一致性
  • 异常处理与任务调度
  • 线程间协作与同步

如果管理不当,就可能引发:死锁、竞态条件、跨平台行为不一致等问题。
改进方案:使用线程池。在Java中,最常见的改进是使用 ExecutorService,通过线程池,不需要手动管理线程,而是让框架完成调度以及线程资源复用。

// 线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
executor.submit(() -> {System.out.println("这是一个通过线程池启动的线程。");
});
executor.shutdown();// 更好的方案是自定义线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(4,                       // corePoolSize8,                       // maximumPoolSize60L,                     // keepAliveTimeTimeUnit.SECONDS,new LinkedBlockingQueue<>(100), // 有界队列new ThreadFactoryBuilder().setNameFormat("biz-worker-%d").build(),new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
  • 禁止使用 Executors.newFixedThreadPool 等工厂方法,因为默认是无界队列,高并发时容易 OOM。
  • 生产场景一定要设置线程名,方便排查问题。

2. 不了解 Java 内存模型(JMM)

问题现象:假设有两个线程调用了下面这个简单程序,我们期望 reader() 总能读到最新的flag值,实际有时读不到。

class MyClass {boolean flag = false;public void writer() {flag = true;}public void reader() {if (flag) {System.out.println("Flag is true!");}}
}

原因分析:问题出在Java 内存模型(每个线程有工作内存,类似CPU缓存;变量写入主内存后,其他线程不一定立刻可见;指令可能被重排序优化)上。在没有显式同步的情况下,一个线程对变量的修改,可能对另一个线程不可见reader() 可能一直读到旧值。

解决方案:使用 volatile。如果需要保证变量更新的可见性,可以使用 volatile关键字。volatile 保证可见性,写入结果对其他线程立刻可见。还禁止指令重排序,确保写入操作在读操作之前完成。但不保证原子性。

class MyClass {volatile boolean flag = false;public void writer() {flag = true;}public void reader() {if (flag) {System.out.println("Flag is true!");}}
}

3. 混淆并发(Concurrency)与并行(Parallelism)

用了多线程,程序并一定会更快。

  • 并发:指同时处理多个任务,在单核 CPU上通过切换实现。
  • 并行:指多个任务真正同时执行,需要多核 CPU支持。

如果程序要完成的任务主要是 I/O 密集型(如读写数据库、网络请求),并发可以显著提升性能。
如果是 CPU 密集型(如图像处理、大量计算),必须依赖真正的并行,才能加速。

// 示例:I/O 密集型
CompletableFuture.supplyAsync(this::queryDB).thenAccept(this::sendResponse);// CPU 密集型, 并行计算
ForkJoinPool pool = new ForkJoinPool();
pool.submit(() -> imageFiles.parallelStream().forEach(this::processImage));

4. 滥用 synchronized

问题现象: 在不理解锁的情况下随意加 synchronized,虽然看似是安全的做法,但会使得应用运行变得缓慢。

public synchronized void doWork() {// 业务逻辑
}

原因分析:过度使用 synchronized 会导致:

  • 线程竞争:所有线程抢同一把锁。
  • 性能下降:尤其在高并发场景。
  • 死锁风险:多线程互相等待锁释放。

解决方案

  • 使用JDK 提供的高性能 并发集合,如 ConcurrentHashMap
  • 使用 读写锁 ReadWriteLock,区分读写场景
  • 使用 原子类 AtomicInteger / AtomicLong 处理简单计数,性能更高,也更安全
// 示例:用原子类代替 synchronized
AtomicInteger count = new AtomicInteger(0);public void increment() {count.incrementAndGet();
}// 示例:对于读多写少场景,使用 ReadWriteLock
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
try {// safe read
} finally {lock.readLock().unlock();
}

5. 没有用“多线程思维”设计程序

问题现象:刚开始写多线程程序时,还是会沿用一步一步执行,假设操作有序的线性编程思维。但多线程意味着多个步骤可能同时进行,如果不考虑线程安全,会导致出现bug。一些典型错误如:

  • 无锁更新共享变量
  • 假设操作顺序是固定的
  • 不考虑竞态条件

建议:线程安全优先。编写多线程程序时要考虑:

  1. 有哪些共享状态
  2. 谁会读?谁会写?
  3. 如果两个线程同时修改,会怎样?
/*
示例:银行账户提现
看似安全,但如果两个线程同时通过了 balance >= amount 判断,可能超提。
*/
class BankAccount {private int balance = 1000;public synchronized void withdraw(int amount) {if (balance >= amount) {balance -= amount;}}
}// 解决办法:使用乐观锁或数据库事务来保证一致性。
AtomicInteger balance = new AtomicInteger(1000);
public void withdraw(int amount) {int old;do {old = balance.get();if (old < amount) throw new RuntimeException("余额不足");} while (!balance.compareAndSet(old, old - amount));
}

6. 没有线程异常处理

问题现象:线程中的异常,默认会被悄悄吞掉

// 主线程不会感知,异常就消失了
new Thread(() -> {throw new RuntimeException("线程发生异常!");
}).start();

解决方案一:统一异常捕获。

// 使用自定义 ThreadFactory 设置 UncaughtExceptionHandler
ThreadFactory factory = r -> {Thread t = new Thread(r);t.setUncaughtExceptionHandler((th, ex) -> log.error("线程异常", ex));return t;
};

解决方案二:使用 Future

Future<?> future = executor.submit(() -> {throw new RuntimeException("线程发生异常");
});
try {future.get();
} catch (ExecutionException e) {e.printStackTrace();
}

7. 没有正确测试多线程代码

问题现象:多线程代码测试起来的难度比较大,普通单元测试并不可靠。多线程bug常为低概率偶现,单次测试不足以发现问题。

解决方案

  • 使用 Awaitility 等工具,等待条件满足
  • 在生产环境引入随机延迟,模拟真实压力
// 示例:使用 Awaitility,相比Thread.sleep,这种写法更健壮。
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> sharedList.size() == 10);

8. 没有很好利用Java的新型并发工具

Java 并发编程已经大幅演进,有了更多更好用的工具类。

  • CompletableFuture:简化异步编程
  • ForkJoinPool:递归并行任务
  • 结构化并发(Structured Concurrency):JDK21 引入的新特性
  • 虚拟线程(Virtual Threads, Project Loom):降低并发成本
// 示例:使用 CompletableFuture,无需直接操纵线程池,异步任务写法更直观。
CompletableFuture.supplyAsync(() -> "从线程返回的结果").thenAccept(System.out::println);
http://www.dtcms.com/a/349953.html

相关文章:

  • Spring拦截器中@Resource注入为null的问题
  • Github热门开源项目榜单 - 2025年07月
  • 【c++】leetcode300 最长递增子序列
  • 二、添加3D形状
  • Springboot应用如何与SkyWalking集成,并使用Docker进行发布
  • 深入理解Linux进程程序替换:从原理到实践
  • Elasticsearch JVM调优:核心参数与关键技巧
  • Git克隆时遇到“Filename too long“错误的完美解决方案
  • 代理设计模式
  • 俄罗斯情报机构推出新型安卓恶意软件,伪装成杀毒软件
  • SciPy科学计算与应用:SciPy入门与应用-科学计算与NumPy协同实践
  • 工业异常检测大模型(1)数据集、方法
  • 【git使用场景】本地仓库与远程仓库存在独立历史
  • Vulkan 学习路线图
  • Git 怎么仓库迁移?error: remote origin already exists.怎么解决
  • 定时器的原理
  • TensorFlow 深度学习 | Dataset API 数据读取详解
  • Open3D入门指南:3D数据处理与可视化利器
  • 初识神经网络——《深度学习入门:基于Python的理论与实现》
  • 昆仑万维开源 Matrix-3D大模型,正在开启“造物主”模式
  • 【智慧城市】2025年中国地质大学(武汉)暑期实训优秀作品(2):智慧城市西安与一带一路
  • pytest 并发执行用例(基于受限的测试资源)
  • imx6ull-驱动开发篇40——Linux RTC 驱动简介
  • 一道MySQL笔试题: 输出 100 以内质数
  • VIVO/OPPO手机,显示5G开关
  • 【SystemUI】锁屏来通知默认亮屏Wake模式
  • Mac 菜单栏多合一工具自荐:FancyTool
  • LeetCode算法日记 - Day 22: 提莫攻击、Z字形变换
  • 电影感人文街拍摆摊纪实摄影后期Lr调色教程,手机滤镜PS+Lightroom预设下载!
  • 从手术室到街头摄像头:多模态融合如何让AI“看得懂”万物?