1. 引言:为什么需要多线程
现代计算机普遍采用多核 CPU,单线程程序无法充分利用硬件资源。在服务端领域,高并发、低延迟是核心诉求,多线程成为必然选择。Java 从语言层面提供了强大的多线程支持(java.lang.Thread),并在 java.util.concurrent 包中提供了丰富的并发工具。但多线程也带来了线程安全、死锁、上下文切换开销等问题,因此理解其适用场景至关重要。
Java 多线程在 Web 异步处理、并行计算、高并发服务器、定时任务、数据库连接池、消息队列消费、文件处理、缓存更新、批处理、GUI 编程及分布式系统等场景的应用。涵盖了线程池配置、CompletableFuture、锁机制、并发工具类及设计模式等关键技术点,并强调了线程安全、死锁预防及资源管理的重要性。
现代计算机普遍采用多核 CPU,单线程程序无法充分利用硬件资源。在服务端领域,高并发、低延迟是核心诉求,多线程成为必然选择。Java 从语言层面提供了强大的多线程支持(java.lang.Thread),并在 java.util.concurrent 包中提供了丰富的并发工具。但多线程也带来了线程安全、死锁、上下文切换开销等问题,因此理解其适用场景至关重要。
在 Web 应用中,某些操作耗时较长且不需要立即返回结果给客户端,例如用户注册成功后发送欢迎邮件、短信通知,或者记录操作日志。如果在请求线程中同步执行这些操作,会延长接口响应时间,降低用户体验。
将耗时操作提交到独立的线程池中异步执行,主线程立即返回成功响应。
@RestController
public class UserController {
@Autowired
private ThreadPoolTaskExecutor taskExecutor;
@PostMapping("/register")
public String register(User user) {
// 保存用户到数据库(同步)
userService.save(user);
// 异步发送邮件
taskExecutor.submit(() -> emailService.sendWelcomeMail(user));
return "注册成功";
}
}
CompletableFuture 进行异步编程,支持链式调用和组合。CompletableFuture.runAsync(() -> emailService.sendWelcomeMail(user), executor);
处理大量数据时,例如统计百万级用户的消费总额、生成复杂报表、图像处理、机器学习特征工程等,单线程耗时过长。通过将数据分片,多线程并行处理,最后合并结果。
parallelStream(),底层使用 ForkJoinPool。CountDownLatch 或 CompletableFuture 等待完成。// 使用并行流计算总和
long sum = LongStream.rangeClosed(1, 10_000_000)
.parallel()
.sum();
// 手动分片
List<List<Data>> partitions = partition(dataList, 1000);
List<CompletableFuture<Result>> futures = partitions.stream()
.map(partition -> CompletableFuture.supplyAsync(() -> process(partition), executor))
.collect(Collectors.toList());
List<Result> results = futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
Web 容器(如 Tomcat)为每个请求分配一个线程,从而实现并发处理。NIO 模型也依赖线程池管理连接。此外,RPC 框架(如 Dubbo)的服务端和客户端也大量使用线程池处理网络事件。
# Spring Boot 默认使用 Tomcat,可配置最大线程数
server.tomcat.max-threads=200
server.tomcat.max-connections=10000
系统需要定期执行某些任务,如每天凌晨统计数据、每小时清理临时文件、每分钟检查超时订单。这些任务通常由调度线程池执行。
@Scheduled(cron = "0 0 1 * * ?") // 每天凌晨 1 点执行
public void generateReport() {
// 生成日报
}
@Scheduled(fixedDelay) 避免重叠)。数据库连接是稀缺资源,频繁创建/关闭开销大。连接池(如 HikariCP、Druid)维护一组连接,允许多个线程并发获取和释放连接。
连接池内部使用线程安全的集合(如 ConcurrentLinkedQueue、LinkedBlockingQueue)管理连接。线程通过 getConnection() 获取连接,使用后 close() 归还。
// HikariCP 配置示例
HikariConfig config = new HikariConfig();
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
HikariDataSource dataSource = new HikariDataSource(config);
消息中间件(如 Kafka、RabbitMQ)通常支持多线程消费,以提高处理速度。每个消费者可以启动多个线程处理不同分区或消息。
@KafkaListener(topics = "orders", concurrency = "3") // 启动 3 个消费者线程
public void consume(Order order) {
// 处理订单
}
// 分片计算文件 MD5
ExecutorService executor = Executors.newFixedThreadPool(4);
List<Future<byte[]>> futures = new ArrayList<>();
for (int i = 0; i < 4; i++) {
long start = i * chunkSize;
long end = Math.min(start + chunkSize, fileSize);
futures.add(executor.submit(() -> computeChunkMD5(file, start, end)));
}
在高并发系统中,缓存(如 Redis、本地 Caffeine)失效时,若大量请求同时穿透到数据库,可能导致数据库压力暴增(缓存雪崩)。多线程加载是一种常见解决方案:当缓存失效时,只允许一个线程去加载数据,其他线程等待。
synchronized、ReentrantLock)控制只有一个线程加载。Future 缓存加载任务,后续线程等待同一个 Future 的结果。// 使用 ConcurrentHashMap 缓存 Future
ConcurrentHashMap<String, Future<Object>> cache = new ConcurrentHashMap<>();
public Object getData(String key) {
Future<Object> future = cache.get(key);
if (future == null) {
FutureTask<Object> task = new FutureTask<>(() -> loadFromDB(key));
future = cache.putIfAbsent(key, task);
if (future == null) {
future = task;
task.run(); // 执行加载
}
}
return future.get(); // 等待结果
}
ETL 任务、数据迁移、报表生成等场景需要处理数百万甚至数亿条记录。单线程处理耗时过长,多线程分片处理可以显著缩短时间。
TaskExecutorStep)。// 多线程数据迁移
int totalCount = countRecords();
int pageSize = 1000;
int threadCount = 10;
int pagesPerThread = totalCount / (pageSize * threadCount);
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
for (int i = 0; i < threadCount; i++) {
int startPage = i * pagesPerThread;
executor.submit(() -> migratePage(startPage, pageSize));
}
Swing、JavaFX 等桌面应用,如果在事件调度线程(EDT)中执行耗时操作(如网络请求、文件读写),界面会冻结。需要将耗时操作放到后台线程,完成后更新 UI。
doInBackground() 中执行耗时操作,通过 publish()/process() 更新 UI。call() 中执行任务,使用 Platform.runLater() 更新 UI。// JavaFX 示例
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
// 耗时操作
return null;
}
};
new Thread(task).start();
在微服务架构中,一个服务可能需要调用多个下游服务。如果串行调用,总耗时等于各服务耗时之和。使用多线程并行调用可以大幅降低延迟。
CompletableFuture<User> userFuture = CompletableFuture.supplyAsync(() -> userService.getUser(id), userPool);
CompletableFuture<Order> orderFuture = CompletableFuture.supplyAsync(() -> orderService.getOrder(id), orderPool);
CompletableFuture<Void> all = CompletableFuture.allOf(userFuture, orderFuture);
all.join(); // 等待两个都完成
User user = userFuture.get();
Order order = orderFuture.get();
ThreadLocal 的继承性或 TransmittableThreadLocal)。BlockingQueue(如 ArrayBlockingQueue、LinkedBlockingQueue)。BlockingQueue<Task> queue = new LinkedBlockingQueue<>(100);
// 生产者
new Thread(() -> {
while (true) {
Task task = createTask();
queue.put(task);
}
}).start();
// 消费者
new Thread(() -> {
while (true) {
Task task = queue.take();
process(task);
}
}).start();
ThreadPoolExecutor。FutureTask、CompletableFuture。ReentrantReadWriteLock、StampedLock。CountDownLatch latch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
try {
doWork();
} finally {
latch.countDown();
}
});
}
latch.await(); // 等待所有任务完成
Semaphore semaphore = new Semaphore(10); // 允许 10 个线程同时访问
semaphore.acquire();
try {
accessResource();
} finally {
semaphore.release();
}
thenApply、thenCombine、exceptionally 等,极大简化了异步编程。ReentrantReadWriteLock。CountDownLatch 和 CyclicBarrier,支持动态注册线程。synchronized、Lock、原子类、并发集合(ConcurrentHashMap、CopyOnWriteArrayList)确保共享数据安全。tryLock),保持锁顺序一致。UncaughtExceptionHandler 或使用 Future 的 get() 捕获。Java 多线程在项目中的应用无处不在,从后端服务到客户端应用,从异步处理到并行计算,每一个场景都需要深入理解并发原理并谨慎设计。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online