线程创建与生命周期
概述
线程(Thread) 是程序执行的最小单位。一个 Java 进程可以包含多个线程,它们共享进程的内存空间,从而能够并发执行多段代码。掌握线程的创建与生命周期,是学习 同步与锁、线程池 的前提。本文介绍两种创建线程的方式、线程的六种状态以及 start、run、sleep、join 等常用方法。
线程的两种创建方式
Java 中创建并运行一条新线程,通常有两种方式:继承 Thread 类 或 实现 Runnable 接口。推荐使用实现 Runnable,便于与类继承体系解耦、便于与线程池等 API 配合。
方式一:继承 Thread 类
子类继承 java.lang.Thread,重写 run(),在 run() 中编写线程要执行的逻辑;然后创建该子类实例并调用 start() 启动线程。
public class MyThread extends Thread {
@Override
public void run() {
// 线程要执行的代码
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 启动新线程,由新线程执行 run()
// 主线程继续往下执行
System.out.println("main 线程");
}
}注意
必须调用 start() 才会在新线程中执行 run()。若直接调用 run(),只是在当前线程中普通方法调用,不会创建新线程。
方式二:实现 Runnable 接口(推荐)
实现 Runnable 接口的 run(),将实现类实例传给 Thread 的构造方法,再调用 Thread#start() 启动。
public class RunnableDemo {
public static void main(String[] args) {
Runnable task = () -> {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
};
Thread t = new Thread(task, "工作线程");
t.start();
}
}提示
实现 Runnable 后,同一个 Runnable 实例可以传给多个 Thread,多线程共享同一段逻辑(若逻辑内部有共享变量,则需考虑同步与锁)。且类可以继续继承其他类,不受 Java 单继承限制。
基本示例
示例 1:区分 start() 与 run()
public class StartVsRun {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("执行线程: " + Thread.currentThread().getName()));
// 错误做法:直接调用 run(),只在 main 线程中执行
t.run(); // 输出: 执行线程: main
// 正确做法:start() 会启动新线程并执行 run()
t.start(); // 输出: 执行线程: Thread-0(或类似名称)
}
}示例 2:为线程命名与 sleep
public class NamedThreadDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 开始");
try {
Thread.sleep(1000); // 当前线程休眠 1 秒,不释放锁
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " 结束");
}, "我的工作线程");
t.start();
t.join(); // 主线程等待 t 结束
System.out.println("main 结束");
}
}Thread.sleep(long millis) 会抛出受检异常 InterruptedException,需捕获或声明抛出;在 catch 中通常调用 Thread.currentThread().interrupt() 恢复中断状态,便于上层处理。
示例 3:join() 等待线程结束
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
try {
Thread.sleep(500);
System.out.println("子线程完成");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
t.start();
System.out.println("主线程等待子线程...");
t.join(); // 主线程阻塞,直到 t 终止
System.out.println("主线程继续");
}
}线程生命周期与状态
Java 中线程在任意时刻处于以下六种状态之一(Thread.State 枚举):
| 状态 | 说明 |
|---|---|
| NEW | 已创建未启动(new Thread() 后未调用 start()) |
| RUNNABLE | 可运行(包括正在运行或就绪等待 CPU 调度) |
| BLOCKED | 等待获取监视器锁(如等待进入 synchronized 块) |
| WAITING | 无限期等待(如 Object.wait()、Thread.join() 无参) |
| TIMED_WAITING | 限时等待(如 Thread.sleep()、Thread.join(long)、Object.wait(long)) |
| TERMINATED | 线程已结束(run() 正常返回或抛出未捕获异常) |
状态转换可简记为:NEW → RUNNABLE ↔ BLOCKED/WAITING/TIMED_WAITING → TERMINATED。调用 start() 后从 NEW 进入 RUNNABLE;在等待锁或调用 sleep/join/wait 时进入 BLOCKED/WAITING/TIMED_WAITING;run() 结束后进入 TERMINATED。
Thread t = new Thread(() -> {});
System.out.println(t.getState()); // NEW
t.start();
System.out.println(t.getState()); // RUNNABLE(或很快变为 TIMED_WAITING 等,视实现而定)常用方法速览
| 方法 | 说明 |
|---|---|
start() | 启动线程,由 JVM 在新线程中调用 run() |
run() | 线程要执行的逻辑;不应直接调用,应通过 start() 间接执行 |
static sleep(long millis) | 当前线程休眠指定毫秒,进入 TIMED_WAITING |
join() / join(long millis) | 当前线程等待本线程终止(无限或限时) |
static currentThread() | 获取当前执行线程的引用 |
getName() / setName(String) | 线程名称(便于调试与日志) |
getId() | 线程 ID(唯一,不可变) |
getState() | 当前状态(Thread.State) |
isAlive() | 是否已启动且未终止 |
补充
若需要线程返回结果,可使用 Callable + Future,在线程池中提交任务并获取返回值;详见 线程池。
注意事项
注意
- 不要直接调用
run():只有start()才会真正启动新线程;直接run()只是同步方法调用。 - 不要对同一 Thread 实例多次调用
start():一旦线程已启动或已终止,再次start()会抛出IllegalThreadStateException。
提示
- 线程命名:构造时传入名称或
setName(),便于日志与排查问题。 - 捕获
InterruptedException时,通常应调用Thread.currentThread().interrupt()恢复中断状态,以便上层或后续代码能感知中断。
相关链接
- 同步与锁 — synchronized、Lock 与线程安全
- 线程池 — 使用线程池管理线程
- 并发工具类简介 — JUC 常用工具
- Oracle Java 教程 - 并发