1. 为什么需要深入理解Java线程?
在Java开发中,线程就像餐厅里的服务员。想象一家只有单个服务员的餐馆(单线程)和一家有多个服务员的餐馆(多线程)的区别 - 后者能同时处理更多顾客(任务)的需求。但增加服务员(线程)也带来了新的挑战:如何避免服务员之间互相干扰?如何确保他们高效协作?这正是Java线程编程的核心课题。
我见过太多开发者停留在Thread和Runnable的基础使用上,当面对真实的生产环境问题时往往束手无策。实际上,Java线程体系远比表面看起来复杂得多 - 从JVM内存模型到CPU指令重排序,从锁优化到线程间通信,每个环节都藏着魔鬼般的细节。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Java线程的核心机制剖析
2.1 线程生命周期与状态转换
Java线程的生命周期远不止简单的"新建-运行-终止"三阶段。通过Thread.getState()可以观察到精确的状态枚举:
java复制public enum State {
NEW, // 创建但未start()
RUNNABLE, // 可运行(可能在等待CPU)
BLOCKED, // 等待监视器锁
WAITING, // 无限期等待
TIMED_WAITING, // 有时限等待
TERMINATED; // 终止
}
关键状态转换场景:
- 从NEW到RUNNABLE:调用start()方法
- 从RUNNABLE到BLOCKED:竞争synchronized锁失败
- 从RUNNABLE到WAITING:调用Object.wait()或Thread.join()
- 从WAITING到RUNNABLE:被notify()/notifyAll()唤醒
重要提示:直接调用run()方法不会启动新线程,而是在当前线程同步执行!
2.2 线程创建的四种方式对比
- 继承Thread类(已过时)
java复制class MyThread extends Thread {
public void run() {
System.out.println("Thread running");
}
}
// 使用
new MyThread().start();
- 实现Runnable接口(推荐基础方式)
java复制class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable running");
}
}
// 使用
new Thread(new MyRunnable()).start();
- 实现Callable接口(支持返回值)
java复制class MyCallable implements Callable<String> {
public String call() throws Exception {
return "Callable result";
}
}
// 使用
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
System.out.println(task.get()); // 获取返回值
- 线程池创建(生产环境首选)
java复制ExecutorService executor = Executors.newFixedThreadPool(4);
executor.submit(() -> {
System.out.println("From thread pool");
});
executor.shutdown();
方式对比表:
| 创建方式 | 返回值支持 | 异常处理 | 资源消耗 | 适用场景 |
|---|---|---|---|---|
| 继承Thread | 不支持 | 受限 | 高 | 不推荐使用 |
| 实现Runnable | 不支持 | 受限 | 中 | 简单任务 |
| 实现Callable | 支持 | 完善 | 中 | 需要结果的异步任务 |
| 线程池 | 支持 | 完善 | 低 | 生产环境首选 |
3. 线程安全与同步机制实战
3.1 synchronized的深水区
表面上看,synchronized用法很简单:
java复制// 实例方法同步
public synchronized void method1() {...}
// 静态方法同步
public static synchronized void method2() {...}
// 同步代码块
public void method3() {
synchronized(this) {...}
}
但实际使用时有几个关键陷阱:
- 锁对象选择:错误地使用不同对象作为锁会导致同步失效。例如:
java复制// 错误示范 - 每次new不同的锁对象
public void addItem(List<String> list) {
Object lock = new Object(); // 每次调用新建对象
synchronized(lock) {
list.add("item");
}
}
-
锁粒度问题:过度使用类级别锁(static synchronized)会导致性能瓶颈。我曾优化过一个支付系统,将类锁改为细粒度实例锁后,TPS提升了300%。
-
重入特性:Java中的synchronized是可重入锁,同一线程可以重复获取已持有的锁。这看似简单,但在继承场景下可能产生意外:
java复制class Parent {
public synchronized void parentMethod() {...}
}
class Child extends Parent {
public synchronized void childMethod() {
super.parentMethod(); // 可以成功获取锁
}
}
3.2 volatile关键字的正确理解
volatile经常被误解为"轻量级synchronized",实际上它的核心作用是:
- 保证可见性:写入volatile变量会立即刷新到主内存
- 禁止指令重排序:建立happens-before关系
典型应用场景 - 双重检查锁定(DCL):
java复制class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
如果没有volatile修饰,由于指令重排序,其他线程可能获取到未初始化完成的对象。
3.3 CAS与原子类
Java.util.concurrent.atomic包下的原子类(如AtomicInteger)采用CAS(Compare-And-Swap)机制,比锁更轻量:
java复制AtomicInteger counter = new AtomicInteger(0);
// 线程安全的自增
counter.incrementAndGet();
// CAS操作示例
int oldValue = counter.get();
while(!counter.compareAndSet(oldValue, oldValue + 1)) {
oldValue = counter.get();
}
CAS的ABA问题解决方案 - AtomicStampedReference:
java复制AtomicStampedReference<String> ref = new AtomicStampedReference<>("A", 0);
// 解决ABA问题
int[] stampHolder = new int[1];
String currentRef = ref.get(stampHolder);
ref.compareAndSet(currentRef, "B", stampHolder[0], stampHolder[0]+1);
4. 线程池的深度使用与调优
4.1 线程池核心参数解析
ThreadPoolExecutor的完整构造函数:
java复制public ThreadPoolExecutor(
int corePoolSize, // 核心线程数
int maximumPoolSize, // 最大线程数
long keepAliveTime, // 空闲线程存活时间
TimeUnit unit, // 时间单位
BlockingQueue<Runnable> workQueue, // 工作队列
ThreadFactory threadFactory, // 线程工厂
RejectedExecutionHandler handler // 拒绝策略
)
关键参数组合策略:
-
CPU密集型任务(如加密计算):
- corePoolSize = CPU核心数 + 1
- 使用有界队列(ArrayBlockingQueue)
- 拒绝策略:CallerRunsPolicy
-
IO密集型任务(如网络请求):
- corePoolSize = CPU核心数 × 2
- 使用无界队列(LinkedBlockingQueue)
- 拒绝策略:AbortPolicy
4.2 自定义线程池实践
生产环境推荐手动创建线程池,而非使用Executors快捷方法:
java复制ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, // core
8, // max
30, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
new CustomThreadFactory(), // 自定义线程命名
new ThreadPoolExecutor.AbortPolicy() {
// 自定义拒绝处理
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
logger.warn("Task rejected: " + r.toString());
}
});
自定义线程工厂示例:
java复制class CustomThreadFactory implements ThreadFactory {
private final AtomicInteger counter = new AtomicInteger(1);
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Worker-" + counter.getAndIncrement());
t.setUncaughtExceptionHandler((thread, throwable) -> {
logger.error("Uncaught exception in " + thread.getName(), throwable);
});
return t;
}
}
4.3 线程池监控与调优
通过扩展ThreadPoolExecutor实现监控:
java复制class MonitorThreadPool extends ThreadPoolExecutor {
// ...构造函数
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
monitor.logStart(t.getName(), r);
}
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
monitor.logEnd(r, t);
}
}
关键监控指标:
- 活跃线程数:getActiveCount()
- 任务队列大小:getQueue().size()
- 已完成任务数:getCompletedTaskCount()
- 拒绝任务数:自定义统计
5. 并发工具类的高级应用
5.1 CountDownLatch vs CyclicBarrier
两者都用于线程协调,但适用场景不同:
| 特性 | CountDownLatch | CyclicBarrier |
|---|---|---|
| 重置能力 | 不可重置 | 可循环使用 |
| 等待机制 | 线程等待计数减到0 | 线程相互等待到达屏障点 |
| 计数方向 | 递减 | 递增 |
| 典型应用 | 启动准备检查 | 分阶段任务 |
CountDownLatch示例 - 并行初始化:
java复制CountDownLatch latch = new CountDownLatch(3);
// 三个初始化任务
for (int i = 0; i < 3; i++) {
new Thread(() -> {
doInitWork();
latch.countDown();
}).start();
}
// 等待初始化完成
latch.await();
startService();
CyclicBarrier示例 - 多阶段计算:
java复制CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached barrier");
});
for (int i = 0; i < 3; i++) {
new Thread(() -> {
phase1Work();
barrier.await();
phase2Work();
barrier.await();
}).start();
}
5.2 CompletableFuture的异步编排
Java 8引入的CompletableFuture提供了强大的异步编程能力:
链式调用示例:
java复制CompletableFuture.supplyAsync(() -> queryFromDB()) // 异步查询
.thenApply(result -> processData(result)) // 同步处理
.thenAcceptAsync(processed -> saveToCache(processed)) // 异步保存
.exceptionally(ex -> {
logger.error("Processing failed", ex);
return null;
});
多任务组合:
java复制CompletableFuture<String> future1 = queryService1();
CompletableFuture<String> future2 = queryService2();
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> {
String result1 = future1.join();
String result2 = future2.join();
combineResults(result1, result2);
});
5.3 ThreadLocal的内存泄漏防范
ThreadLocal使用不当会导致严重的内存泄漏:
正确用法:
java复制// 使用static final修饰
private static final ThreadLocal<SimpleDateFormat> dateFormatHolder =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
// 使用后必须remove
try {
dateFormatHolder.get().format(new Date());
} finally {
dateFormatHolder.remove(); // 关键!
}
内存泄漏分析:
- Thread → ThreadLocalMap → Entry (key弱引用, value强引用)
- 当ThreadLocal对象被回收后,key变为null,但value仍然存在强引用
- 如果线程长期存活(如线程池线程),会导致value无法回收
解决方案:
- 使用static final修饰ThreadLocal实例
- 每次使用后调用remove()清理
- 考虑使用FastThreadLocal(Netty实现)
