Thread 类详解:核心属性、常用方法与实践
1)Thread 的定位:管理一条线程执行流的对象
Thread 对象用于描述并管理一条线程执行流。线程真正的调度由底层系统完成,但在 Java 层面创建、启动、命名、观测与中断协作都通过 Thread 完成。
2)创建与启动:start() 才是启动新线程
常见构造方式:
java
Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("worker-1");
Thread t4 = new Thread(new MyRunnable(), "worker-1");
关键点只有一个:
start()才会创建新线程并进入调度- 直接调用
run()不会创建新线程,只是普通方法调用
示例:
java
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName());
}, "worker-1");
t.start(); // ✅ 开新线程
// t.run(); // ❌ 不会开新线程
3)Thread 的核心属性与用法(重点)
常用属性可以理解为三类问题:
- "是谁":ID、Name
- "在干嘛":State、Alive
- "怎么调度与退出":Priority、Daemon、Interrupted
3.1 线程 ID:getId()(唯一、只读)
含义 :每个线程有唯一 ID。
用法:
java
long id = t.getId();
实践:日志打印线程 ID 有助于定位并发问题(线程名可能重复,ID 更稳定)。
3.2 线程名:getName()(建议强制规范命名)
含义 :线程名是排障时的"路标"。
用法:
java
String name = t.getName();
实践 :推荐"业务-角色-序号"命名,例如 pay-worker-1、coupon-cleaner-0。(构造时直接传 name 最干净。)
3.3 线程状态:getState()(观测用)
含义 :返回 Thread.State,用于观测线程处于哪个阶段。
用法:
java
Thread.State s = t.getState();
实践 :状态适合诊断,不建议作为业务分支条件(状态瞬时变化,读到即过时)。
直觉判断:
- 大量
BLOCKED→ 锁竞争严重 - 大量
WAITING/TIMED_WAITING→ 在等条件、等线程结束、或 sleep
3.4 优先级:getPriority()(提示而非承诺)
含义 :优先级高的线程"更可能"获得调度机会,但不保证。
用法:
java
int p = t.getPriority();
实践:不要依赖优先级保证正确性;顺序、互斥、时序应由同步与协作机制保证。
3.5 守护线程属性:isDaemon() / setDaemon(...)(前台/后台线程差异就在这)
这一块非常关键,因为它直接决定 JVM 什么时候退出。
3.5.1 前台线程 vs 后台线程(守护线程)
-
前台线程(用户线程,non-daemon) :默认创建出来的线程就是前台线程。
只要 JVM 中还存在任何一个前台线程,JVM 就不会退出。 -
后台线程(守护线程,daemon) :通常承担监控、清理、心跳、统计上报等"服务型/陪跑型"任务。
当 JVM 中只剩守护线程时,JVM 会直接退出,守护线程会被"硬停",不保证把工作做完。
一句话:前台线程负责"续命",守护线程不续命。
3.5.2 setDaemon 的硬规则
t.setDaemon(true):设为守护线程t.setDaemon(false):设为前台线程(默认)
两条硬规则必须记牢:
- 必须在
start()之前调用 ,否则会抛IllegalThreadStateException - 默认会继承创建它的线程的 daemon 属性 (例如
main是前台线程,main创建的线程默认也是前台)
3.5.3 示例:守护线程在"只剩它一个"时被 JVM 结束
java
public class DaemonDemo1 {
public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
while (true) {
System.out.println("daemon still running...");
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {}
}
}, "daemon-worker");
t.setDaemon(true); // ✅ 必须在 start 前设置
t.start();
Thread.sleep(800);
System.out.println("main ends");
// main 结束后,如果 JVM 里只剩 daemon 线程,JVM 直接退出
}
}
3.5.4 对照:前台线程会阻止 JVM 退出
java
public class DaemonDemo2 {
public static void main(String[] args) throws Exception {
Thread t = new Thread(() -> {
while (true) {
System.out.println("user thread running...");
try {
Thread.sleep(200);
} catch (InterruptedException ignored) {}
}
}, "user-worker");
// 默认就是前台线程
t.start();
Thread.sleep(800);
System.out.println("main ends");
// main 结束后仍有前台线程,所以 JVM 不退出
}
}
3.5.5 时机错误:start() 后再 setDaemon 会抛异常
java
public class DaemonDemo3 {
public static void main(String[] args) {
Thread t = new Thread(() -> {}, "bad-daemon");
t.start();
// ❌ 已经 start 了,再设置 daemon 会抛 IllegalThreadStateException
t.setDaemon(true);
}
}
3.5.6 工程取舍建议
- 适合守护线程:监控采样、指标上报、定期清理、心跳探测、非关键日志刷新
- 不适合守护线程:写盘落库、事务提交、关键数据同步、订单/支付等必须完成的任务
原因:JVM 退出时守护线程可能被硬停,收尾不可靠。
3.6 是否存活:isAlive()(线程是否已结束)
含义 :线程是否处于存活状态(通常可理解为 run 是否结束)。
用法:
java
boolean alive = t.isAlive();
实践:不推荐用忙等轮询:
java
while (t.isAlive()) {} // 不推荐:空转浪费 CPU
等待结束应使用 join()(见下文)。
3.7 中断标记:interrupt() / isInterrupted()(协作式停止)
含义 :中断不是强杀线程,而是发出"停止请求",由线程自行检查并退出。
用法:
java
t.interrupt(); // 发出中断请求
boolean flag = t.isInterrupted(); // 查询中断标记(不清除)
两种典型表现:
- 线程在
sleep/wait/join等阻塞点:常见结果是抛出InterruptedException - 线程在正常执行:不抛异常,仅设置中断标记,需要自行检测
推荐"优雅停止"写法:
java
class Worker implements Runnable {
@Override
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// do work...
}
} finally {
// 清理资源
}
}
}
4)常用方法:高频三件套
4.1 获取当前线程:Thread.currentThread()
java
Thread cur = Thread.currentThread();
System.out.println(cur.getName());
用于打印"当前执行流是谁",尤其适合日志与调试。
4.2 休眠:Thread.sleep(ms)
java
Thread.sleep(1000);
sleep 只能保证实际休眠时间 ≥ 指定值;不要把它当精确计时器。
4.3 等待线程结束:join()
java
t.join(); // 等待 t 执行结束
// t.join(1000); // 最多等待 1000ms
比 isAlive() 忙等更正确、更省 CPU,也更符合线程协作语义。
5)线程属性"体检模板"(排障常用)
java
Thread t = new Thread(() -> {
System.out.println("running...");
}, "diag-1");
System.out.println("id=" + t.getId());
System.out.println("name=" + t.getName());
System.out.println("state=" + t.getState());
System.out.println("priority=" + t.getPriority());
System.out.println("daemon=" + t.isDaemon());
System.out.println("alive=" + t.isAlive());
System.out.println("interrupted=" + t.isInterrupted());
t.start();
6)总结
- 线程必须命名:线程名是最便宜的可观测性
- 停止线程用中断协作:
interrupt+ 周期性检查 +finally清理 - 等待结束用
join:不要写忙等轮询 - 状态用于诊断:不要用
getState()控制业务逻辑 - 守护线程要谨慎:只用于"可丢弃/可中断"的后台任务,关键任务禁止 daemon
这套 Thread 属性与方法,本质上就是并发程序的"仪表盘"。把仪表读懂,很多问题就能从"玄学"降级成"可推理的工程问题"。