在当代软件开发中,多线程技术早已从可选技能变成了必备能力。我处理过的一个电商促销系统案例就很典型——单线程处理用户请求时,QPS(每秒查询率)勉强达到200就卡死,而通过合理多线程改造后轻松突破5000。这种性能的指数级提升,正是多线程的魅力所在。
Java作为企业级开发的主力语言,其多线程支持经历了三个重要演进阶段:
这三种实现方式各有适用场景,就像工具箱里的不同工具。新手常犯的错误是认为"新的一定更好",而实际上在定时任务等场景下,直接继承Thread反而更直观。接下来我会结合10年踩坑经验,带你看透这三种方式的本质区别。
java复制class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程ID:" + Thread.currentThread().getId());
}
}
// 启动方式
new MyThread().start();
这种方式的优势在于简单直接,我在教学时总是从这里入手。但实际项目中会发现几个致命缺陷:
关键经验:适合快速原型验证,但企业级项目慎用。我曾见过某金融系统滥用Thread继承导致类爆炸(300+线程类),维护成了噩梦。
java复制class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("执行线程:" + Thread.currentThread().getName());
}
}
// 启动方式
new Thread(new MyRunnable()).start();
这是目前最主流的做法,其核心优势在于:
实测数据显示,使用线程池执行Runnable任务,创建1万个任务仅需约400毫秒,效率提升5倍以上。但要注意共享资源访问问题:
java复制// 错误示例:多线程共享计数器
class Counter {
private int count;
public void add() { count++; } // 非原子操作
}
避坑指南:务必使用synchronized或Atomic类保证原子性。我曾调试过一个计数器少算的bug,最终发现是未做同步导致。
java复制class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "结果时间:" + System.currentTimeMillis();
}
}
// 执行方式
ExecutorService executor = Executors.newCachedThreadPool();
Future<String> future = executor.submit(new MyCallable());
System.out.println(future.get()); // 阻塞获取结果
这是最强大的方式,特点包括:
在最近的一个大数据处理项目中,我们使用Callable实现了这样的流程:
性能数据:对比三种方式在10万次任务处理的耗时:
- 纯Thread:32秒(内存占用高)
- Runnable+线程池:8秒
- Callable+Future:9秒(但具备结果处理能力)
java复制// 典型错误场景
class VisibilityDemo {
boolean ready = false; // 应该加volatile
void writer() {
ready = true;
}
void reader() {
while(!ready); // 可能死循环
System.out.println("可见了");
}
}
解决方案:
java复制// 三种同步方案对比
class AtomicDemo {
private int syncCount = 0;
private AtomicInteger atomicCount = new AtomicInteger(0);
private volatile int volatileCount = 0;
// 方案1:synchronized方法
public synchronized void syncAdd() { syncCount++; }
// 方案2:CAS原子操作
public void atomicAdd() { atomicCount.incrementAndGet(); }
// 错误方案:volatile不保证原子性
public void volatileAdd() { volatileCount++; }
}
性能测试数据(100万次递增):
通过jstack发现的典型死锁:
text复制Found one Java-level deadlock:
"Thread-2":
waiting to lock monitor 0x00007f88e4003e58 (object 0x000000076ab270c8, a java.lang.Object),
which is held by "Thread-1"
"Thread-1":
waiting to lock monitor 0x00007f88e4003d18 (object 0x000000076ab270d8, a java.lang.Object),
which is held by "Thread-2"
预防策略:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
5, // 核心线程数(CPU密集型建议N+1)
10, // 最大线程数(IO密集型建议2N)
60L, TimeUnit.SECONDS, // 空闲线程存活时间
new ArrayBlockingQueue<>(100), // 任务队列
Executors.defaultThreadFactory(), // 线程工厂
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
配置经验公式:
血泪教训:某次大促因使用默认AbortPolicy导致大量请求失败,后改为CallerRunsPolicy实现平稳降级。
java复制// 监控示例
ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newCachedThreadPool();
System.out.println("活跃线程:" + pool.getActiveCount());
System.out.println("完成任务:" + pool.getCompletedTaskCount());
System.out.println("队列大小:" + pool.getQueue().size());
System.out.println("池大小:" + pool.getPoolSize());
System.out.println("最大池大小:" + pool.getLargestPoolSize());
建议监控指标:
java复制CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenAccept(System.out::println)
.exceptionally(ex -> {
System.err.println("出错:" + ex.getMessage());
return null;
});
常用组合方法:
java复制CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try { Thread.sleep(2000); }
catch (InterruptedException e) { /* 处理中断 */ }
return "结果";
});
try {
future.get(1, TimeUnit.SECONDS); // 超时抛出TimeoutException
} catch (TimeoutException e) {
future.cancel(true); // 中断执行线程
}
java复制// 1. 查询订单
CompletableFuture<Order> orderFuture = CompletableFuture
.supplyAsync(() -> orderService.getOrder(id), ioPool);
// 2. 并行查询用户和商品
CompletableFuture<User> userFuture = orderFuture
.thenApplyAsync(Order::getUserId)
.thenApplyAsync(userService::getUser, ioPool);
CompletableFuture<Product> productFuture = orderFuture
.thenApplyAsync(Order::getProductId)
.thenApplyAsync(productService::getProduct, ioPool);
// 3. 合并结果
CompletableFuture<OrderDetail> detailFuture = orderFuture
.thenCombineAsync(userFuture, (o,u) -> new OrderDetail(o,u), cpuPool)
.thenCombineAsync(productFuture, (d,p) -> d.setProduct(p), cpuPool);
// 4. 超时控制
detailFuture.orTimeout(3, TimeUnit.SECONDS);
这种模式相比传统回调嵌套的优势:
java复制class LeakDemo {
static ThreadLocal<byte[]> local = new ThreadLocal<>();
void leak() {
local.set(new byte[1024 * 1024]); // 1MB内存
// 忘记remove()
}
}
当使用线程池时,线程会被复用,导致:
java复制try {
local.set(value);
// 业务逻辑
} finally {
local.remove(); // 必须执行
}
java复制ThreadLocal<WeakReference<BigObject>> local = new ThreadLocal<>();
java复制// JIT会优化掉这个锁
public String concat(String s1, String s2) {
StringBuffer sb = new StringBuffer();
synchronized(sb) { // 可被消除
sb.append(s1);
sb.append(s2);
}
return sb.toString();
}
错误示例:
java复制class BigLock {
public synchronized void processA() { /* 耗时操作 */ }
public synchronized void processB() { /* 快速操作 */ }
}
优化方案:
java复制class FineGrainedLock {
private final Object lockA = new Object();
private final Object lockB = new Object();
public void processA() {
synchronized(lockA) { /* 只锁必要部分 */ }
}
public void processB() {
synchronized(lockB) { /* 不影响processA */ }
}
}
性能对比:
| 容器类型 | 特点 | 适用场景 | 性能基准 |
|---|---|---|---|
| Hashtable | 全表锁 | 遗留系统 | 1800 ops/s |
| Collections.synchronizedMap | 方法锁 | 简单场景 | 2100 ops/s |
| ConcurrentHashMap | 分段锁 | 高并发读 | 9800 ops/s |
| ConcurrentSkipListMap | 有序 | 范围查询 | 4500 ops/s |
| 队列类型 | 是否有界 | 排序规则 | 适用场景 |
|---|---|---|---|
| ArrayBlockingQueue | 是 | FIFO | 固定资源池 |
| LinkedBlockingQueue | 可选 | FIFO | 可扩展任务队列 |
| PriorityBlockingQueue | 否 | 优先级 | 任务调度系统 |
| SynchronousQueue | 特殊 | 直接传递 | 高吞吐交换 |
| DelayQueue | 是 | 延迟时间 | 定时任务 |
优点:
缺点:
使用建议:适合读多写少且数据量小的场景(如监听器列表),实测在1000个元素时写性能下降约60%。