线程基础与生命周期
线程是并发编程的最小执行单元。在深入锁和并发工具之前,必须先理解线程本身——如何创建、如何调度、有哪些状态、状态之间如何转换。这篇文章从底层出发,把线程的生命周期讲透。
创建线程的三种方式
1. 继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行: " + Thread.currentThread().getName());
}
}
new MyThread().start(); // 必须调用 start(),而非 run()
调用 start() 才会创建新线程并在新线程中执行 run()。直接调用 run() 只是在当前线程执行普通方法,不会创建新线程。
缺点:Java 单继承,继承了 Thread 就无法继承其他类。
2. 实现 Runnable 接口
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程运行: " + Thread.currentThread().getName());
}
}
new Thread(new MyRunnable()).start();
// Lambda 简写(JDK 8+)
new Thread(() -> System.out.println("Lambda 形式")).start();
推荐方式。任务(Runnable)与线程(Thread)解耦,同一个 Runnable 可以提交给多个线程或线程池。
3. 实现 Callable + FutureTask
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "任务结果";
}
}
FutureTask<String> task = new FutureTask<>(new MyCallable());
new Thread(task).start();
String result = task.get(); // 阻塞等待结果
Callable 与 Runnable 的核心区别:
| 特性 | Runnable | Callable |
|---|---|---|
| 返回值 | void(无返回值) |
泛型 V(有返回值) |
| 受检异常 | 不能抛 | 可以抛 |
| 配合 | Thread / Executor |
FutureTask / ExecutorService |
底层本质:只有一种方式
无论哪种方式,最终都是通过 Thread.start() 创建线程。start() 会调用本地方法 start0(),由 JVM 调用操作系统的线程创建 API:
Thread.start()
└── start0() (native method)
└── JVM: os::create_thread()
└── Linux: pthread_create()
└── Windows: CreateThread()
所以严格来说,Java 中创建线程只有一种方式——new Thread().start()。Runnable 和 Callable 只是定义任务的方式,不是创建线程的方式。
线程的六种状态
Java 线程的状态定义在 java.lang.Thread.State 枚举中,共六种:
public enum State {
NEW, // 新建:线程对象创建但未启动
RUNNABLE, // 可运行:正在运行或等待 CPU 时间片
BLOCKED, // 阻塞:等待获取 synchronized 锁
WAITING, // 等待:无限期等待另一个线程的通知
TIMED_WAITING, // 超时等待:有时间限制的等待
TERMINATED // 终止:线程执行完毕或异常退出
}
状态转换全景图
stateDiagram-v2
[*] --> NEW: new Thread()
NEW --> RUNNABLE: start()
RUNNABLE --> TERMINATED: run() 结束 / 异常退出
state RUNNABLE {
[*] --> Ready
Ready --> Running: 调度器切换
Running --> Ready: 时间片用完
}
RUNNABLE --> BLOCKED: 等待进入 synchronized 块
BLOCKED --> RUNNABLE: 获得锁
RUNNABLE --> WAITING: wait() / join() / park()
WAITING --> RUNNABLE: notify() / unpark() / 中断
RUNNABLE --> TIMED_WAITING: sleep(ms) / wait(ms) / join(ms)
TIMED_WAITING --> RUNNABLE: 超时 / notify() / unpark() / 中断
每种状态的触发条件
NEW → RUNNABLE:调用 thread.start()。
RUNNABLE → BLOCKED:线程试图进入一个被其他线程持有锁的 synchronized 代码块。注意:只有 synchronized 会导致 BLOCKED 状态,ReentrantLock 的加锁等待是 WAITING 状态(因为它使用 LockSupport.park())。
RUNNABLE → WAITING:
Object.wait():释放锁并等待通知Thread.join():等待另一个线程结束LockSupport.park():挂起当前线程
RUNNABLE → TIMED_WAITING:
Thread.sleep(ms):休眠指定时间(不释放锁)Object.wait(ms):超时等待(释放锁)Thread.join(ms):超时等待另一个线程LockSupport.parkNanos(ns)/parkUntil(ms)
BLOCKED → RUNNABLE:获取到 synchronized 锁。
WAITING / TIMED_WAITING → RUNNABLE:
Object.notify()/notifyAll()LockSupport.unpark(thread)- 超时到期
join()的目标线程结束
RUNNABLE → TERMINATED:run() 方法正常结束或抛出未捕获异常。
RUNNABLE 的内涵
Java 的 RUNNABLE 状态其实对应操作系统层面的两种状态:**Running(正在执行)**和 Ready(就绪,等待 CPU)。Java 没有区分这两种状态,因为线程获得/失去 CPU 时间片的频率极高(毫秒级),区分意义不大。
Java: RUNNABLE
↕
OS: Ready ←→ Running(由 OS 调度器切换)
同样,操作系统层面的"I/O 等待"状态在 Java 中也归入 RUNNABLE。例如一个线程在执行 socket.read() 阻塞时,Java 层面仍然是 RUNNABLE 状态,但操作系统层面是 Sleeping/Waiting。
关键方法深度解析
sleep() vs wait()
这是底层架构中最核心的对比之一:
| 特性 | Thread.sleep(ms) |
Object.wait() |
|---|---|---|
| 所属类 | Thread(静态方法) | Object(实例方法) |
| 释放锁 | 不释放 | 释放 |
| 唤醒方式 | 时间到自动唤醒 | notify() / notifyAll() |
| 使用前提 | 无 | 必须在 synchronized 块中 |
| 线程状态 | TIMED_WAITING | WAITING / TIMED_WAITING |
为什么 wait() 必须在 synchronized 块中?
wait() 的语义是"释放锁并等待通知"。如果不在 synchronized 块中,当前线程根本没有持有锁,释放什么?更关键的是,如果允许在 synchronized 块外调用 wait/notify,会导致 Lost Wakeup(丢失唤醒) 问题:
// 假设 wait() 不需要 synchronized(伪代码,实际会抛异常)
// 线程 A:
if (condition == false) {
// ← 线程 A 在这里被挂起(还没执行到 wait)
obj.wait(); // 线程 B 的 notify 已经发出,A 永远收不到了
}
// 线程 B:
condition = true;
obj.notify(); // ← 通知已发出,但 A 还没 wait
将 wait/notify 放在 synchronized 块中,通过持有锁来保证检查条件和进入等待是一个原子操作。
wait/notify 的正确用法
// 等待方(消费者)
synchronized (lock) {
while (!condition) { // 必须用 while,而非 if
lock.wait();
}
// 条件满足,执行业务逻辑
}
// 通知方(生产者)
synchronized (lock) {
condition = true;
lock.notifyAll(); // 推荐 notifyAll 防止饥饿
}
为什么用 while 而非 if? 因为存在虚假唤醒(Spurious Wakeup)——线程可能在没有 notify 的情况下被唤醒(操作系统层面的实现细节)。用 while 循环可以在被唤醒后再次检查条件。
join() 的本质
join() 底层就是 wait():
// Thread.join() 源码简化
public final synchronized void join(long millis) throws InterruptedException {
while (isAlive()) {
wait(millis); // 当前线程在目标线程对象上 wait
}
}
当目标线程结束时,JVM 会调用该线程对象的 notifyAll(),从而唤醒所有在其上 wait() 的线程。
interrupt() 中断机制
Java 没有提供强制停止线程的方法(Thread.stop() 已废弃,不安全)。正确的做法是使用中断机制——一种协作式的停止方式。
Thread t = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 执行工作
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// sleep 被中断时会清除中断标志,需要重新设置
Thread.currentThread().interrupt();
break;
}
}
System.out.println("线程正常退出");
});
t.start();
// ... 一段时间后
t.interrupt(); // 设置中断标志
中断的三种响应:
- 线程处于 WAITING / TIMED_WAITING:
sleep()、wait()、join()等方法会抛出InterruptedException,并清除中断标志。 - 线程处于 RUNNABLE:仅设置中断标志位,线程需要主动检查
isInterrupted()并自行退出。 - 线程处于 BLOCKED(等待 synchronized 锁):中断不会生效,线程继续等待锁。这是
synchronized的一个缺陷,ReentrantLock.lockInterruptibly()解决了这个问题。
Thread.stop() 为什么被废弃?
stop() 会强制释放线程持有的所有锁,但不保证共享数据的一致性。想象一个线程正在执行银行转账(扣款已完成,入款尚未完成),此时 stop() 会导致钱凭空消失。
// 极端危险!
synchronized (lock) {
accountA.debit(100); // 已扣款
// ← Thread.stop() 在这里杀死线程,锁被释放
accountB.credit(100); // 入款永远不会执行
}
线程间通信方式
| 方式 | 机制 | 适用场景 |
|---|---|---|
synchronized + wait/notify |
隐式锁 | 简单的等待-通知 |
ReentrantLock + Condition |
显式锁 | 需要多个等待队列 |
BlockingQueue |
队列 | 生产者-消费者模式 |
CountDownLatch |
倒计数 | 等待 N 个线程完成 |
CyclicBarrier |
栅栏 | N 个线程互相等待到齐 |
Semaphore |
信号量 | 限制并发数 |
Exchanger |
交换 | 两个线程交换数据 |
volatile 标志位 |
变量可见性 | 简单的停止信号 |
守护线程(Daemon Thread)
Java 中的线程分为用户线程和守护线程。守护线程是为其他线程提供服务的后台线程:当所有用户线程结束时,守护线程会被自动终止,JVM 随之退出。
Thread daemon = new Thread(() -> {
while (true) {
// 后台任务,如日志收集、心跳检测
}
});
daemon.setDaemon(true); // 必须在 start() 之前设置
daemon.start();
典型的守护线程:GC 线程、Finalizer 线程、NIO 的 Selector 线程。
注意:不要在守护线程中执行 I/O 操作或依赖
finally块清理资源——JVM 退出时会直接杀死守护线程,finally块不保证执行。
生产环境核心踩坑点
| 问题 | 要点 |
|---|---|
| 创建线程有几种方式? | 本质上只有一种(Thread.start),任务定义有 Runnable 和 Callable |
| sleep() 和 wait() 的区别? | 最核心:sleep 不释放锁,wait 释放锁 |
| 线程有几种状态? | 6 种(NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED) |
| 为什么 wait() 必须在 synchronized 块中? | 防止 Lost Wakeup,保证条件检查和等待的原子性 |
| 如何优雅停止线程? | interrupt() + isInterrupted() 检查,不用 stop() |
| Thread.stop() 为什么废弃? | 强制释放锁导致数据不一致 |
| BLOCKED 和 WAITING 的区别? | BLOCKED 是等待 synchronized 锁,WAITING 是主动进入等待 |