1. 线程停止的三种方式与实战陷阱
线程停止看似简单,实则暗藏杀机。在Java中,Thread.stop()方法虽然存在但已被标记为@Deprecated,原因在于它的暴力终止会导致对象状态不一致。我曾在一个支付系统中亲眼目睹因为粗暴停止线程导致的事务状态锁死——那个凌晨三点排查问题的经历让我深刻理解了线程停止的正确姿势。
1.1 标志位中断法(推荐方案)
最优雅的方式是通过volatile boolean标志位控制线程退出。下面是一个电商订单处理的典型示例:
java复制class OrderProcessor implements Runnable {
private volatile boolean running = true;
public void stop() {
running = false;
}
@Override
public void run() {
while(running) {
// 处理订单逻辑
if(Thread.currentThread().isInterrupted()) {
// 清理资源
break;
}
}
}
}
关键细节:volatile保证可见性,避免指令重排序导致的死循环
1.2 interrupt()中断机制
当线程处于阻塞状态(如sleep/wait)时,标志位方案会失效。这时需要interrupt():
java复制Thread worker = new Thread(() -> {
try {
while(!Thread.currentThread().isInterrupted()) {
TimeUnit.SECONDS.sleep(1); // 可能抛出InterruptedException
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 重新设置中断标志
}
});
worker.interrupt(); // 外部中断
1.3 致命陷阱:stop()的替代方案
有些开发者会用以下危险方式停止线程:
java复制// 反模式!绝对不要这样做
thread.stop();
thread.suspend();
System.exit(0);
这些方法会导致:
- 锁永远无法释放(Monitor锁泄漏)
- 文件流未关闭(资源泄漏)
- 对象状态不一致(业务数据错乱)
2. 线程协作的四大核心场景
2.1 生产者-消费者模式(阻塞队列实现)
我在物流调度系统中实现的智能分拣方案:
java复制BlockingQueue<Package> queue = new ArrayBlockingQueue<>(100);
// 生产者
new Thread(() -> {
while(true) {
queue.put(new Package()); // 自动阻塞
}
}).start();
// 消费者
new Thread(() -> {
while(true) {
Package pkg = queue.take(); // 自动阻塞
// 处理包裹
}
}).start();
实战经验:队列大小要根据内存和业务吞吐量平衡,过小会导致频繁阻塞
2.2 CountDownLatch在分布式启动中的应用
微服务启动时需要等待依赖服务就绪:
java复制CountDownLatch latch = new CountDownLatch(3);
// 三个健康检查线程
services.forEach(service ->
new Thread(() -> {
if(service.healthCheck()) {
latch.countDown();
}
}).start());
latch.await(30, TimeUnit.SECONDS); // 主线程等待
2.3 CyclicBarrier实现多阶段任务
银行对账系统的典型应用:
java复制CyclicBarrier barrier = new CyclicBarrier(3, () -> {
// 所有线程到达后执行汇总
});
IntStream.range(0, 3).forEach(i ->
new Thread(() -> {
// 阶段一:下载对账文件
barrier.await();
// 阶段二:比对交易记录
barrier.await();
}).start());
2.4 Semaphore限流实战
防止爬虫被目标网站封禁:
java复制Semaphore semaphore = new Semaphore(5); // 并发上限
void crawl() {
semaphore.acquire();
try {
// 执行爬取
} finally {
semaphore.release();
}
}
3. 线程异常处理的黄金法则
3.1 未捕获异常处理机制
金融系统中必须设置的保险措施:
java复制Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
logger.error("Thread {} died: {}", t.getName(), e);
// 触发告警通知
AlarmService.notifyDevOps(e);
});
ExecutorService pool = Executors.newCachedThreadPool(r -> {
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(...);
return t;
});
3.2 Future的异常捕获技巧
异步任务处理的最佳实践:
java复制Future<?> future = executor.submit(() -> {
// 可能抛出异常的任务
});
try {
future.get();
} catch (ExecutionException e) {
Throwable realCause = e.getCause();
// 处理实际异常
}
3.3 CompletableFuture异常链
电商优惠券发放的容错方案:
java复制CompletableFuture.supplyAsync(() -> {
// 第一阶段:校验用户资格
throw new RuntimeException("校验失败");
}).exceptionally(e -> {
// 异常处理
return fallbackResult;
}).thenApplyAsync(result -> {
// 第二阶段:发放优惠券
});
4. 高阶线程池调优实战
4.1 线程池参数动态调整
基于Apache Dubbo的线程池优化案例:
java复制ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 根据CPU核心数动态计算
50, // 峰值流量考虑
60, TimeUnit.SECONDS,
new ResizableCapacityBlockingQueue<>(1000) // 自定义可扩容队列
);
// 运行时动态调整
pool.setCorePoolSize(newCoreSize);
pool.setMaximumPoolSize(newMaxSize);
4.2 线程池监控方案
自研监控组件的关键指标采集:
java复制new ScheduledThreadPoolExecutor(1).scheduleAtFixedRate(() -> {
Map<String, Number> metrics = Map.of(
"activeCount", pool.getActiveCount(),
"queueSize", pool.getQueue().size(),
"completedTasks", pool.getCompletedTaskCount()
);
Monitor.report(metrics);
}, 0, 1, TimeUnit.SECONDS);
4.3 虚拟线程实战(Java 19+)
高并发IO密集型服务改造:
java复制try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // 自动关闭
5. 死锁预防与诊断体系
5.1 死锁检测工具链
生产环境诊断方案:
bash复制# 1. 生成线程转储
jstack <pid> > thread.dump
# 2. 使用JConsole可视化分析
# 3. 集成Arthas实时诊断
thread -b # 检测死锁
5.2 锁排序避免死锁
银行转账的安全实现:
java复制void transfer(Account from, Account to, BigDecimal amount) {
Account first = from.compareTo(to) < 0 ? from : to;
Account second = from.compareTo(to) < 0 ? to : from;
synchronized(first) {
synchronized(second) {
// 转账操作
}
}
}
5.3 锁超时机制
分布式锁的容错方案:
java复制if(lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 临界区
} finally {
lock.unlock();
}
} else {
// 降级处理
}
6. 线程本地存储的进阶用法
6.1 TransmittableThreadLocal实战
线程池上下文传递方案:
java复制TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
// 父线程设置
context.set("trace-id-123");
executor.execute(TtlRunnable.get(() -> {
// 子线程可读取
String traceId = context.get();
}));
6.2 FastThreadLocal优化
Netty中的高性能实现:
java复制FastThreadLocal<ByteBuf> bufferCache = new FastThreadLocal<>() {
@Override
protected ByteBuf initialValue() {
return PooledByteBufAllocator.DEFAULT.buffer();
}
};
// 使用时直接获取
ByteBuf buf = bufferCache.get();
7. 线程安全设计模式
7.1 不可变对象模式
支付交易快照实现:
java复制@Immutable
public final class PaymentSnapshot {
private final String txId;
private final BigDecimal amount;
// 只有构造时赋值
public PaymentSnapshot(String txId, BigDecimal amount) {
this.txId = txId;
this.amount = amount;
}
// 无setter方法
}
7.2 线程特定存储模式
用户会话隔离方案:
java复制ThreadLocal<UserSession> sessionHolder = new ThreadLocal<>();
void handleRequest(Request req) {
sessionHolder.set(new UserSession(req.getUserId()));
try {
// 业务处理
} finally {
sessionHolder.remove();
}
}
8. 并发调试技巧大全
8.1 确定性复现技巧
通过随机种子强制复现并发问题:
java复制// 设置固定的随机种子
Random debugRandom = new Random(12345);
// 在并发操作中使用
int delay = debugRandom.nextInt(100);
Thread.sleep(delay);
8.2 并发单元测试方案
使用JCStress框架测试竞态条件:
java复制@JCStressTest
@Outcome(id = "1, 0", expect = Expect.ACCEPTABLE)
@State
public class CounterTest {
private int x;
@Actor
void thread1(II_Result r) {
r.r1 = ++x;
}
@Actor
void thread2(II_Result r) {
r.r2 = ++x;
}
}
在15年开发生涯中,我总结出一条铁律:线程问题都是设计问题。最近处理的一个生产事故——因为错误使用ThreadLocal导致的内存泄漏,再次验证了这一点。建议每个开发者都要建立自己的并发问题检查清单,在代码评审时逐项核对。记住:没有完美的线程方案,只有适合场景的权衡选择。
