Thread
2025/12/19大约 4 分钟约 1311 字
创建线程的方式
1. 继承 Thread 类
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
// 使用
MyThread t = new MyThread();
t.start();2. 实现 Runnable 接口
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行: " + Thread.currentThread().getName());
}
}
// 使用
Thread t = new Thread(new MyRunnable());
t.start();
// Lambda 写法
new Thread(() -> System.out.println("Lambda 线程")).start();3. 实现 Callable 接口 + FutureTask
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(1000);
return 100;
}
}
// 使用
Callable<Integer> callable = new MyCallable();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
// 获取返回值(阻塞等待)
Integer result = futureTask.get();
System.out.println("结果: " + result);4. 线程池
// 创建线程池
ExecutorService executor = Executors.newFixedThreadPool(4);
// 提交 Runnable
executor.execute(() -> System.out.println("Runnable 任务"));
// 提交 Callable,获取 Future
Future<Integer> future = executor.submit(() -> {
Thread.sleep(1000);
return 100;
});
Integer result = future.get();
// 关闭线程池
executor.shutdown();对比总结
| 方式 | 返回值 | 异常 | 说明 |
|---|---|---|---|
| 继承 Thread | 无 | 不能抛受检异常 | 单继承限制 |
| 实现 Runnable | 无 | 不能抛受检异常 | 推荐,解耦 |
| Callable + FutureTask | 有 | 可以抛异常 | 需要返回值时使用 |
| 线程池 | 可选 | 可选 | 生产环境推荐 |
线程的状态
Java 线程有 6 种状态,定义在 Thread.State 枚举中:
- NEW - 新建,尚未启动
- RUNNABLE - 可运行(就绪或运行中)
- BLOCKED - 阻塞,等待获取 synchronized 锁
- WAITING - 无限等待
- TIMED_WAITING - 超时等待
- TERMINATED - 终止
状态流转
start()
NEW ────────────────────> RUNNABLE ──────────> TERMINATED
│ ▲ run()结束
│ │
┌───────────────────────────┼──┴────────────────────┐
│ │ │
▼ │ ▼
BLOCKED │ WAITING
(等待synchronized锁) │ (wait/join/park)
│ │ │
└───── 获取到锁 ────────────┘ │
│ │
│ notify/unpark │
└───────────────────────┘
▲
│
TIMED_WAITING
(sleep/wait(ms)/join(ms))
超时或被唤醒状态转换条件
| 转换 | 触发条件 |
|---|---|
| NEW → RUNNABLE | 调用 start() |
| RUNNABLE → BLOCKED | 等待获取 synchronized 锁 |
| BLOCKED → RUNNABLE | 获取到锁 |
| RUNNABLE → WAITING | wait()、join()、LockSupport.park() |
| WAITING → RUNNABLE | notify()、notifyAll()、unpark() |
| RUNNABLE → TIMED_WAITING | sleep(ms)、wait(ms)、join(ms) |
| TIMED_WAITING → RUNNABLE | 超时或被提前唤醒 |
| RUNNABLE → TERMINATED | run() 正常结束或抛出异常 |
BLOCKED vs WAITING
| 对比 | BLOCKED | WAITING |
|---|---|---|
| 原因 | 等待获取 synchronized 锁 | 主动调用 wait/join/park |
| 唤醒 | 获取到锁自动唤醒 | 需要 notify/unpark |
| 释放锁 | 没拿到锁,无需释放 | wait() 会释放锁 |
// BLOCKED:等待获取锁
synchronized (lock) { // 如果锁被占用,进入 BLOCKED
// ...
}
// WAITING:主动等待
synchronized (lock) {
lock.wait(); // 主动进入 WAITING,释放锁
}线程常用方法
sleep() vs wait()
| 对比项 | sleep() | wait() |
|---|---|---|
| 所属类 | Thread | Object |
| 类型 | 静态方法 | 实例方法 |
| 释放锁 | 不释放 | 释放 |
| 使用位置 | 任意 | synchronized 块内 |
| 唤醒方式 | 时间到自动 | notify/notifyAll |
| 线程状态 | TIMED_WAITING | WAITING |
// sleep - 不释放锁
synchronized (lock) {
Thread.sleep(1000); // 休眠期间,lock 仍被持有
}
// wait - 释放锁
synchronized (lock) {
lock.wait(); // 释放 lock,其他线程可获取
}易错点:wait() 进入的是 WAITING 状态,不要说"睡眠状态"。睡眠是 sleep(),进入 TIMED_WAITING。
join() 方法
作用:让当前线程等待目标线程执行完毕后再继续执行。
Thread t1 = new Thread(() -> {
System.out.println("t1 执行");
});
t1.start();
t1.join(); // main 线程等待 t1 完成
System.out.println("main 继续"); // t1 结束后才执行保证线程顺序执行:
// 必须 start → join 交替
t1.start(); t1.join();
t2.start(); t2.join();
t3.start(); t3.join();易错点:顺序执行必须 start → join 交替,不能先全部 start 再全部 join(那样是并行执行后等待全部结束)。
interrupt() 中断机制
核心:interrupt() 只设置中断标志,不会强制停止线程。
// 正确响应中断
while (!Thread.currentThread().isInterrupted()) {
// 工作...
}
System.out.println("收到中断,安全退出");sleep/wait/join 中被中断:抛出 InterruptedException,并清除中断标志。
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// 中断标志已被清除,需要重新设置
Thread.currentThread().interrupt();
}两个判断方法的区别:
| 方法 | 类型 | 清除标志 |
|---|---|---|
| t.isInterrupted() | 实例方法 | 不清除 |
| Thread.interrupted() | 静态方法 | 清除 |
易错点:Thread.interrupted() 会清除标志,连续调用两次,第二次返回 false。
start() vs run()
| 对比 | start() | run() |
|---|---|---|
| 本质 | 启动新线程 | 普通方法调用 |
| 执行线程 | 新线程 | 当前线程 |
| 调用次数 | 只能一次 | 可多次 |
| 底层 | 调用 native start0() | 直接执行代码 |
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
});
t.run(); // 输出:main(当前线程执行)
t.start(); // 输出:Thread-0(新线程执行)start() 只能调用一次:第二次调用抛出 IllegalThreadStateException,因为线程状态已不是 NEW。
start() 方法执行过程:
Java 层 JVM 层 OS 层
│ │ │
start() │ │
│ │ │
├── 状态检查 │ │
│ (threadStatus != 0?) │ │
│ 不是 NEW 状态抛异常 │ │
│ │ │
├── 加入线程组 │ │
│ │ │
├── start0() ──────────────>│ │
│ (native 方法) │ │
│ ├── 创建 JavaThread 对象 │
│ │ │
│ ├── 创建 OSThread 对象 │
│ │ │
│ ├── os::create_thread() ──>│
│ │ │
│ │ ├── pthread_create()
│ │ │ (Linux)
│ │ │ 或 CreateThread()
│ │ │ (Windows)
│ │ │
│<── start() 返回 ──────────│ │
│ │ │
│ 新线程被 CPU 调度后 │ │
│ │ │
│ ├── 回调 Java 的 run() ──>│
│ │ │- 状态检查:检查 threadStatus,只有 NEW 状态才能启动
- 加入线程组:将线程添加到线程组管理
- 调用 native start0():进入 JVM 层
- JVM 创建线程:创建 JavaThread 和 OSThread 对象
- 调用操作系统 API:pthread_create (Linux) 或 CreateThread (Windows)
- start() 返回:此时新线程可能还没开始执行
- CPU 调度后:新线程获得时间片,JVM 回调 run() 方法
易错点:只能调用一次的原因是线程状态检查,不是 NEW 状态就抛异常。一个 Thread 对象只能代表一个线程生命周期。
易错点汇总
- wait() 进入 WAITING 状态,不是"睡眠状态"
- 顺序执行线程必须 start → join 交替,不能先全部 start
- Thread.interrupted() 会清除中断标志,isInterrupted() 不会
- start() 只能调用一次是因为线程状态检查机制
