Java多线程项目操作:同步、通信与线程池精要
Java多线程核心要点摘要 本文总结了Java多线程编程的核心机制与实践经验。首先介绍了synchronized关键字实现线程同步的正确用法,包括锁对象选择和减小锁粒度;其次讲解了volatile变量的适用场景与限制。在通信机制方面,重点分析了wait/notify的正确使用方式。最后详细讲解了线程池的配置原则,包括参数设置和拒绝策略选择,特别强调避免使用Executors工厂方法。文章还指出了常
Java多线程基础与实战:同步、通信与线程池
前言
在开发个人项目的过程中,随着业务逻辑的复杂化,多线程处理成为了必须面对的问题。无论是保证共享数据的安全性,还是提高系统的并发处理能力,掌握Java多线程的基础知识都至关重要。
本文旨在梳理我目前对Java多线程的理解,重点记录核心机制的原理、代码实现以及在项目中遇到的实际问题。
一、线程同步机制
1.1 synchronized:内置锁的应用
synchronized是Java中最基础的同步关键字,基于对象监视器(Monitor)实现。它具有可重入性,且由JVM自动管理锁的获取与释放,不易出现死锁(相对于手动锁而言)。
代码实现
在项目中处理库存扣减等涉及共享资源的操作时,我通常采用同步代码块的方式,以减小锁的粒度:
public class StockService {
private int stock = 100;
// 定义专用的锁对象,避免锁this带来的潜在风险
private final Object lock = new Object();
public void decreaseStock(int amount) {
// 仅对关键逻辑加锁
synchronized (lock) {
if (stock >= amount) {
stock -= amount;
System.out.println("扣减成功,剩余库存:" + stock);
} else {
System.out.println("库存不足");
}
}
// 耗时操作(如发送通知)放在锁外,避免阻塞其他线程
}
}
注意事项
- 锁对象选择:尽量避免使用字符串常量或全局单例对象作为锁,防止不同业务模块意外竞争同一把锁。
- 静态方法:
synchronized修饰静态方法时,锁的是当前类的Class对象,作用范围是整个JVM,需谨慎使用。
1.2 volatile:可见性与有序性
volatile关键字主要用于保证变量的可见性和禁止指令重排序,但它不保证原子性。
适用场景
在控制线程运行状态的场景中,volatile非常有效。例如,需要一个标志位来优雅地停止后台任务:
public class DataTask implements Runnable {
// 必须使用volatile,确保主线程修改后,工作线程能立即感知
private volatile boolean running = true;
@Override
public void run() {
while (running) {
// 执行任务逻辑
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
System.out.println("任务已停止");
}
public void stop() {
this.running = false;
}
}
误区提醒
切勿使用volatile修饰自增操作(如count++)。因为count++包含读、改、写三个步骤,volatile无法保证这三个步骤的原子性,仍会导致数据不一致。此类场景需配合synchronized或原子类(AtomicInteger)使用。
1.3 关于ReentrantLock
ReentrantLock提供了比synchronized更灵活的功能,如尝试获取锁(tryLock)、中断等待、公平锁等。
现状说明:
由于ReentrantLock需要手动在finally块中释放锁,若处理不当极易引发死锁风险。目前我的项目中暂无特殊锁需求,synchronized已能满足大部分场景。因此,现阶段我优先保证代码的稳健性,后续将在更复杂的并发场景中逐步引入ReentrantLock。
二、线程间通信
2.1 wait/notify机制
wait()和notify()是Object类提供的方法,用于线程间的等待与唤醒。使用时必须配合synchronized,且等待条件判断必须使用while循环,以防止虚假唤醒。
示例:生产者 - 消费者模型
public class Buffer {
private final Queue<Integer> queue = new LinkedList<>();
private final int MAX_SIZE = 10;
public void produce(int item) throws InterruptedException {
synchronized (queue) {
// 队列满时等待
while (queue.size() == MAX_SIZE) {
queue.wait();
}
queue.add(item);
queue.notifyAll(); // 唤醒所有等待线程
}
}
public int consume() throws InterruptedException {
synchronized (queue) {
// 队列空时等待
while (queue.isEmpty()) {
queue.wait();
}
int item = queue.poll();
queue.notifyAll();
return item;
}
}
}
三、线程池实战
在生产环境中,严禁直接使用new Thread()创建线程。频繁创建和销毁线程会消耗大量系统资源,甚至导致OOM。使用ThreadPoolExecutor统一管理线程是标准做法。
3.1 线程池参数配置

在SpringBoot项目中,我通过配置类定义了全局线程池:
@Bean
public ThreadPoolExecutor taskExecutor() {
return new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
60L, // 空闲线程存活时间
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 任务队列
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
}
参数解读
- 核心线程数:线程池中保持存活的线程数量。
- 最大线程数:当队列满时,允许创建的最大线程数。
- 拒绝策略:当队列和线程数都达到上限时的处理方式。
CallerRunsPolicy:由调用线程执行该任务。这种策略既能防止任务丢失,又能通过降低提交速度来缓解系统压力,适合对任务完整性要求较高的场景。
3.2 避坑指南
- 避免使用Executors工厂方法:
Executors.newFixedThreadPool()等方法创建的线程池,其队列长度默认为Integer.MAX_VALUE,可能导致内存溢出。建议始终通过ThreadPoolExecutor构造函数显式创建。 - 资源隔离:不同业务场景(如IO密集型、CPU密集型)建议使用不同的线程池,避免相互影响。
更多推荐



所有评论(0)