线程方法详解
每个方法的用法、坑点、适用场景。
方法分类
线程相关方法可以分为几类:
| 类别 | 方法 | 作用 |
|---|---|---|
| 生命周期 | start() / run() | 启动 / 执行 |
| 等待 | sleep() / join() / wait() | 暂停等待 |
| 调度 | yield() | 礼让 CPU |
| 中断 | interrupt() / isInterrupted() | 打断 / 查询 |
| 状态 | getState() / isAlive() | 查询状态 |
start() vs run()
这是最容易踩的坑。
java
Thread thread = new Thread(() -> {
System.out.println("哪个线程?");
});
thread.start(); // ✅ 启动新线程,异步执行
thread.run(); // ❌ 在当前线程同步执行,没有新线程区别:
| start() | run() | |
|---|---|---|
| 创建新线程 | ✅ | ❌ |
| 异步执行 | ✅ | ❌ |
| 多次调用 | ❌(抛异常) | ✅ |
java
// 多次调用 start() 会怎样?
thread.start();
thread.start(); // IllegalThreadStateException为什么 start() 不能多次调用?
因为 start() 会调用 native 方法 start0(),将线程状态从 NEW 变为 RUNNABLE。状态一旦改变,就不可逆。
sleep():让线程睡一会儿
让当前线程暂停指定时间,不释放锁:
java
// 睡 1 秒
Thread.sleep(1000);
// 睡 1 秒 + 50 万纳秒
Thread.sleep(1000, 500000);特点:
- 静态方法,作用于当前线程
- 不释放锁
- 可被中断(抛 InterruptedException)
java
public class SleepDemo {
public static void main(String[] args) throws InterruptedException {
long start = System.nanoTime();
Thread.sleep(1000);
long end = System.nanoTime();
System.out.println("睡了 " + (end - start) / 1_000_000 + "ms");
}
}sleep() 不释放锁示例:
java
Object lock = new Object();
synchronized (lock) {
System.out.println("拿到锁了");
Thread.sleep(5000); // 睡的时候还拿着锁!
System.out.println("醒了,释放锁");
}
// 其他线程才能进来join():等我先走
等待另一个线程结束:
java
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("线程执行完毕");
} catch (InterruptedException e) {}
});
thread.start();
thread.join(); // 主线程等 thread 结束
System.out.println("主线程继续"); // 等 thread 跑完才执行带超时版本:
java
thread.join(5000); // 等 5 秒就不等了
// 等 thread 结束,最多等 5 秒
boolean finished = thread.join(5, TimeUnit.SECONDS);
if (!finished) {
System.out.println("等不及了,先干别的");
}典型场景:主线程等子线程跑完再汇总结果:
java
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
int[] results = new int[3];
Thread t1 = new Thread(() -> results[0] = compute(1));
Thread t2 = new Thread(() -> results[1] = compute(2));
Thread t3 = new Thread(() -> results[2] = compute(3));
t1.start();
t2.start();
t3.start();
// 三个线程都跑完
t1.join();
t2.join();
t3.join();
System.out.println("所有任务完成,结果: " + Arrays.toString(results));
}
}yield():礼让一下
建议 OS 让出 CPU,让其他线程有机会运行:
java
Thread thread = new Thread(() -> {
for (int i = 0; i < 100; i++) {
// 干一会儿
if (i % 10 == 0) {
Thread.yield(); // 让其他线程插队
}
}
});重要:yield() 只是一个建议,OS 可能完全忽略它。不要依赖 yield() 实现任何同步逻辑。
java
// 错误:依赖 yield 实现公平
while (shouldPause) {
Thread.yield(); // OS 可能根本不鸟你
}interrupt():打断线程
设置中断标志,让线程可以优雅地停止:
java
Thread thread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
// 干活
}
});
thread.start();
thread.interrupt(); // 打断它关键点:interrupt() 不会直接停止线程,只是设置标志位。线程需要主动检查这个标志。
java
// 三种响应中断的方式:
// 方式1:在 while 条件中检查
while (!Thread.interrupted()) {
// 干活
}
// 方式2:捕获 InterruptedException
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 被中断了,恢复中断状态
Thread.currentThread().interrupt();
}
// 方式3:自定义退出标志
volatile boolean running = true;
while (running) {
// 干活
}interrupt() 对阻塞方法的影响:
如果线程在 sleep() / wait() / join() 等方法中阻塞,调用 interrupt() 会让它立即抛出 InterruptedException:
java
Thread thread = new Thread(() -> {
try {
Thread.sleep(10_000); // 睡 10 秒
} catch (InterruptedException e) {
System.out.println("被中断了!");
}
});
thread.start();
Thread.sleep(100);
thread.interrupt(); // 立即让 sleep 抛出异常
thread.join();interrupt() 相关方法
| 方法 | 作用 | 是否清除中断标志 |
|---|---|---|
interrupt() | 设置中断标志 | - |
isInterrupted() | 检查中断标志 | 否 |
interrupted() | 检查并清除中断标志 | 是(静态方法) |
java
// isInterrupted():只检查,不清除
Thread t = new Thread(() -> {});
t.interrupt();
t.isInterrupted(); // true
t.isInterrupted(); // true(还是 true)
// interrupted():检查并清除
Thread t = new Thread(() -> {});
t.interrupt();
Thread.interrupted(); // true,清除了
Thread.interrupted(); // false(已经清除了)优雅停止线程的最佳实践
java
public class GracefulShutdown implements Runnable {
private final AtomicBoolean running = new AtomicBoolean(true);
@Override
public void run() {
while (running.get() && !Thread.currentThread().isInterrupted()) {
try {
doWork();
} catch (InterruptedException e) {
// 收到中断,恢复状态并退出
Thread.currentThread().interrupt();
break;
}
}
cleanup(); // 清理资源
}
public void stop() {
running.set(false);
}
public void shutdown() {
running.set(false);
Thread.currentThread().interrupt(); // 中断正在 sleep/wait 的线程
}
}原则:
- 使用
volatile标志位 +interrupt()双保险 - 捕获
InterruptedException后必须恢复中断状态 - 在
finally块中清理资源
常见错误
错误1:捕获 InterruptedException 后什么都不做
java
// ❌ 错误:中断信息丢失
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 什么都不做,中断信息丢失了
}
// ✅ 正确:恢复中断状态
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
// 或者 throw new RuntimeException(e);
}
// ✅ 也是正确:直接抛出
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}错误2:在同步块中 sleep 并期望释放锁
java
synchronized (lock) {
Thread.sleep(5000); // ❌ 睡的时候还拿着锁!
// 其他想进这个同步块的线程只能干等
}错误3:依赖 join 实现超时
java
// ❌ 错误:join 没有返回值,不知道是真的等完了还是超时了
thread.join(1000);
// ✅ 正确:自己判断
boolean finished = thread.join(1, TimeUnit.SECONDS);
if (!finished) {
System.out.println("超时了");
}方法对照表
| 方法 | 作用于 | 是否释放锁 | 常见用法 |
|---|---|---|---|
start() | 调用者线程 | - | 启动新线程 |
run() | 调用者线程 | - | 线程体 |
sleep(ms) | 当前线程 | 否 | 延时、等待 |
join() | 调用者线程 | 否 | 等待另一个线程 |
yield() | 当前线程 | - | 建议调度 |
interrupt() | 目标线程 | - | 停止线程 |
wait() | 当前线程 | 是 | 线程通信 |
notify() | 等待队列 | - | 唤醒线程 |
总结
start()创建新线程,run()只是普通方法调用sleep()不释放锁,wait()释放锁join()等待线程结束,可以带超时interrupt()设置中断标志,不直接停止线程- 捕获
InterruptedException后必须恢复中断状态 - 优雅停止线程:用
volatile标志 +interrupt()
