Skip to content

线程方法详解

每个方法的用法、坑点、适用场景。

方法分类

线程相关方法可以分为几类:

类别方法作用
生命周期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 的线程
    }
}

原则

  1. 使用 volatile 标志位 + interrupt() 双保险
  2. 捕获 InterruptedException必须恢复中断状态
  3. 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()

基于 VitePress 构建