Java Thread类的API和线程状态详解

理解 Java 线程的 7 种状态(NEW、RUNNABLE、BLOCKED 等)和状态转换是写多线程的基础。Thread 类提供了 start、sleep、join、interrupt 等关键 API,还有不少已废弃的危险方法要避开。

Java 线程的 7 种状态和 Thread 类的几个关键 API,是多线程编程的基础功课。很多人只知道"开启线程用 start"就完事了,不知道线程背后还有这么多状态转换和陷阱。

Java 线程的 7 种状态

线程从诞生到终亡,会经历不同的生命周期状态。java.lang.Thread.State 这个枚举定义了 7 种状态。

NEW

线程对象刚创建,还没调 start()。

Thread t = new Thread(() -> {
    System.out.println("我在跑");
});
System.out.println(t.getState());  // NEW
​

this 时刻线程还没被操作系统调度,就是个 Java 对象而已。

RUNNABLE

调了 start() 之后,线程就进入 RUNNABLE 状态。

注意:RUNNABLE 不等于"正在跑",而是"可以被 CPU 调度运行"。操作系统的线程调度器随时可能让这个线程停下来,去跑其他线程。

Thread t = new Thread(() -> {
    System.out.println("我在跑");
});
t.start();
System.out.println(t.getState());  // RUNNABLE
​

BLOCKED

线程在等待获取一个对象的 monitor 锁(通常是 synchronized 块)。

object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized(lock) {
        try { Thread.sleep(10000); } catch (Exception e) {}
    }
});

Thread t2 = new Thread(() -> {
    try { Thread.sleep(100); } catch (Exception e) {}  // 让 t1 先获得锁
    synchronized(lock) {  // 这里等着呢
        System.out.println("我获得锁了");
    }
});

t1.start();
t2.start();
Thread.sleep(200);
System.out.println(t2.getState());  // BLOCKED
​

一旦拿到锁,就回到 RUNNABLE。

WAITING

线程在调用 wait()、join() 或 LockSupport.park() 后进入这个状态,要等其他线程来唤醒。

Object lock = new Object();

Thread t1 = new Thread(() -> {
    synchronized(lock) {
        try {
            lock.wait();  // 进入 WAITING
        } catch (InterruptedException e) {}
    }
});

Thread t2 = new Thread(() -> {
    try { Thread.sleep(1000); } catch (Exception e) {}
    synchronized(lock) {
        lock.notifyAll();  // 唤醒 t1
    }
});

t1.start();
t2.start();
Thread.sleep(500);
System.out.println(t1.getState());  // WAITING
​

WAITING 和 BLOCKED 的区别很关键:BLOCKED 是在"外面排队"等锁,WAITING 是"已经进去了",主动释放锁去等通知。

TIMED_WAITING

和 WAITING 类似,但有超时时间。比如调 sleep()、wait(timeout)、join(timeout)。

Thread t = new Thread(() -> {
    try {
        Thread.sleep(5000);  // 进入 TIMED_WAITING
    } catch (InterruptedException e) {}
});

t.start();
Thread.sleep(100);
System.out.println(t.getState());  // TIMED_WAITING
​

时间到了或者被中断,就回到 RUNNABLE。

TERMINATED

线程执行完了,或者因异常终止了。这个状态是终点,回不来了。

Thread t = new Thread(() -> {
    System.out.println("我要完了");
});

t.start();
Thread.sleep(100);
System.out.println(t.getState());  // TERMINATED
​

线程状态转换图

NEW
  |
  | start()
  v
RUNNABLE
  |
  +---> BLOCKED (等锁)
  |
  +---> WAITING (wait/join/park)
  |
  +---> TIMED_WAITING (sleep/wait/join with timeout)
  |
  v
TERMINATED
​

Thread 类的关键 API

start() vs run()

千万别直接调 run(),那样不会启动新线程,只是在当前线程里同步执行函数。

Thread t = new Thread(() -> {
    System.out.println("当前线程:" + Thread.currentThread().getName());
});

t.start();  // 正确:启动新线程
// t.run();  // 错误:在当前线程同步执行
​

start() 会由 JVM 调用 native 方法启动真正的操作系统线程,然后那个线程会回调 run() 方法。

sleep(long millis)

让当前线程暂停指定毫秒数,进入 TIMED_WAITING 状态。

Thread.sleep(1000);  // 暂停 1 秒
​

要点:

  • sleep() 不释放锁(如果当前持有的话)
  • 如果被 interrupt,会抛 InterruptedException
  • 是静态方法,只能让当前线程睡,不能让别的线程睡
Object lock = new Object();
new Thread(() -> {
    synchronized(lock) {
        try {
            Thread.sleep(2000);  // 睡 2 秒,但还持着锁
            System.out.println("我醒了,还握着锁呢");
        } catch (InterruptedException e) {}
    }
}).start();

Thread.sleep(500);
new Thread(() -> {
    System.out.println("我想进去");
    synchronized(lock) {
        System.out.println("终于进去了");  // 要等 2 秒
    }
}).start();
​

join()

"等我回来"。调用 t.join() 会阻塞当前线程,直到 t 线程执行完。

Thread t = new Thread(() -> {
    System.out.println("我在干活");
    try { Thread.sleep(2000); } catch (Exception e) {}
    System.out.println("我干完了");
});

t.start();
System.out.println("主线程等等我");
t.join();  // 阻塞在这里,等 t 完成
System.out.println("好的,我回来了");

// 输出
// 主线程等等我
// 我在干活
// 我干完了
// 好的,我回来了
​

join() 也有超时版本 join(long millis)。

interrupt()

给线程发送中断信号,但不是强制停止,而是设置一个中断标志位。

Thread t = new Thread(() -> {
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        System.out.println("我被中断了");
    }
});

t.start();
Thread.sleep(1000);
t.interrupt();  // 发送中断信号

// 输出
// 我被中断了
​

如果线程正在 sleep/wait/join,interrupt() 会立即抛 InterruptedException,线程可以捕获处理。

如果线程正在运行(不在等待状态),interrupt() 只是设置标志,线程可以用 Thread.interrupted() 或 isInterrupted() 来检查:

Thread t = new Thread(() -> {
    while (!Thread.currentThread().isInterrupted()) {  // 检查中断标志
        System.out.println("在干活");
        try { Thread.sleep(100); } catch (InterruptedException e) {}
    }
    System.out.println("停止了");
});

t.start();
Thread.sleep(500);
t.interrupt();
​

setName() / getName()

给线程起名字,方便调试。

Thread t = new Thread(() -> {
    System.out.println("我的名字是:" + Thread.currentThread().getName());
});
t.setName("Worker-1");
t.start();
​

isDaemon() / setDaemon(boolean)

标记线程为守护线程(daemon thread)。

当所有非守护线程都终止了,程序会自动退出,守护线程也会被强制终止(不会等它们完成)。

通常用于后台任务,比如垃圾回收、日志记录。

Thread daemon = new Thread(() -> {
    while (true) {
        System.out.println("我在后台跑");
        try { Thread.sleep(1000); } catch (Exception e) {}
    }
});
daemon.setDaemon(true);  // 要在 start() 前设置
daemon.start();

// 主线程 2 秒后结束
Thread.sleep(2000);
System.out.println("主线程完成,程序退出");
// 输出可能是
// 我在后台跑
// 我在后台跑
// 主线程完成,程序退出
// (守护线程被强制终止,不再输出)
​

已废弃的危险方法

Java 已经废弃(deprecated)了几个 Thread 方法,千万别用:

  • stop():强制停止线程,可能导致数据不一致。应该用 interrupt() + 自己检查标志。
  • suspend() / resume():暂停/恢复线程,容易导致死锁。
  • destroy():销毁线程,几乎不可用。

这些方法为啥被废弃?因为它们都是强制操作,不给线程机会做清理工作,容易破坏数据状态。

一个完整的可中断线程例子

public class GracefulThread extends Thread {
    @Override
    public void run() {
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 做一些工作
                System.out.println("工作中");
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            System.out.println("被中断了");
        } finally {
            // 清理资源
            System.out.println("清理完成");
        }
    }
}

// 使用
GracefulThread t = new GracefulThread();
t.start();
Thread.sleep(3500);
t.interrupt();  // 礼貌地停止
​

这样线程能捕获 interrupt 信号,进行必要的清理后才终止。

小结

Java 线程的 7 种状态和状态转换是核心,记住几个要点:

  • BLOCKED 是外面排队等锁,WAITING 是里面等通知
  • sleep() 不释放锁,wait() 释放锁
  • interrupt() 不是强制停止,而是发信号
  • 别用 stop/suspend/destroy 这些危险方法

理解透彻这些概念,多线程编程就能避开一大堆坑。

更多推荐

章节目录