Java 线程的奇妙之旅:深度解析生命周期与状态

180 阅读8分钟

前言

在Java多线程编程中,理解线程的生命周期和状态是开发高效并发程序的基础。线程作为操作系统调度的基本单位,其状态变化直接影响程序的执行效率和资源管理。本文将深入解析Java线程的生命周期、六种核心状态及其转换规则,并结合代码示例帮助读者掌握实际应用技巧。

image

一、Java线程的生命周期

Java线程的生命周期从创建终止,经历多个状态转换。在JDK 1.5及之后版本中,线程状态由java.lang.Thread.State​枚举类定义,共包含以下6种状态

NEW(新建)
RUNNABLE(可运行)
BLOCKED(阻塞)
WAITING(等待)
TIMED_WAITING(计时等待)
TERMINATED(终止)

image

二、线程的6种状态详解

1. NEW(新建)

  • 定义:线程对象被创建但尚未启动。

  • 触发条件:通过new Thread()​创建线程对象,但未调用start()​方法。

  • 特点

    • 线程未与操作系统线程关联,仅是一个Java对象实例。
    • 无法执行任何代码,直到调用start()​。
  • 示例

    Thread thread = new Thread(() -> System.out.println("线程运行"));
    System.out.println(thread.getState()); // 输出: NEW
    

image

2. RUNNABLE(可运行)

  • 定义:线程已启动,等待CPU调度运行。

  • 触发条件:调用start()​方法后。

  • 特点

    • 包含两种子状态:就绪(Ready)运行(Running)

      • 就绪:线程准备运行,等待CPU时间片分配。
      • 运行:线程获得CPU时间片,正在执行代码。
  • 示例

    thread.start(); 
    System.out.println(thread.getState()); // 可能输出: RUNNABLE
    

image

public class MultiThreadDemo {
​
    public static void main(String[] args) {
​
        // 创建一个新的线程,并通过 Lambda 表达式定义线程的任务
        Thread thread = new Thread(() -> System.out.println("线程运行"));
  
        // 启动线程,线程进入“可运行”状态
        thread.start();
  
        // 输出线程当前的状态,此时线程已经处于“可运行”状态
        System.out.println(thread.getState());
    }
}
​

输出结果:

RUNNABLE
线程运行

为什么先执行System.out.println(thread.getState());,后执行线程任务?

在这段代码中,System.out.println(thread.getState());​ 和 thread.start()​ 是在主线程中顺序执行的,但由于线程的调度和执行是由操作系统管理的,主线程和新创建的子线程是并发执行的。因此,thread.getState() ​ 打印的值是主线程调用 getState()​ 时该线程的状态,而此时线程的状态通常已经是 RUNNABLE​,但实际上它的任务(System.out.println("线程运行")​)可能还没有执行。

解释原因:

  1. start()调用并不会立刻执行线程的任务

    • start()​ 方法只是将线程标记为“可运行”,并且线程会进入“就绪”状态,等待操作系统调度执行。而不是立即开始执行。
    • 线程的实际执行时间完全由操作系统的线程调度机制来控制。可能会发生新线程被调度到执行队列中的时候,主线程就已经执行完了 System.out.println(thread.getState())​。
  2. 主线程与子线程是并发执行的

    • 主线程在执行 thread.start()​ 后,立即继续执行下一行 System.out.println(thread.getState())​,并且此时子线程可能已经进入“就绪”状态(RUNNABLE​),但还没有开始执行任务。
    • 线程调度有时会有延迟,因此你可能会看到“RUNNABLE”状态先被输出,再看到“线程运行”被打印出来。这个顺序的关键在于操作系统如何调度线程。
  3. 线程的状态(RUNNABLE

    • 线程一旦被启动,通常进入 RUNNABLE​ 状态(意味着它已经准备好执行)。然而,实际执行任务的时间点是由线程调度器决定的,主线程调用 getState()​ 时,线程很可能已经进入了 RUNNABLE​ 状态,但尚未开始打印 "线程运行"​。

简单总结:

  • System.out.println(thread.getState())​ 在主线程中执行时,打印的是线程的当前状态,通常是 RUNNABLE​,而子线程可能还没有得到 CPU 时间片来执行任务。
  • 主线程和子线程是并行执行的,线程调度的顺序可能导致打印顺序看起来和你预期的不一样。

3. BLOCKED(阻塞)

  • 定义:线程因等待锁而被阻塞。

  • 触发条件:尝试获取一个被其他线程持有的锁(如synchronized​锁)。

  • 特点

    • 需等待锁释放后,线程才能重新进入RUNNABLE状态。
  • 示例

    public class MultiThreadDemo {
    ​
        public static void main(String[] args) throws InterruptedException {
            // 创建一个专用锁对象,用于线程同步
            // 使用 Object 作为锁是 Java 的常见做法,确保线程安全
            // 作用:控制多个线程对共享资源的访问,避免数据竞争
            Object lock = new Object();
    ​
            // 线程 t1:先获取锁并睡眠
            Thread t1 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 正在持有锁");
                    try {
                        Thread.sleep(2000); // 持有锁 2 秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " 释放锁");
                }
            }, "Thread-t1");
    ​
            // 线程 t2:尝试获取锁,进入 BLOCKED 状态
            Thread t2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + " 获取到锁");
                }
            }, "Thread-t2");
    ​
            // 启动线程
            t1.start();
    ​
            // 等待 t1 进入 RUNNABLE 并获取锁
            Thread.sleep(100);
            System.out.println("t1 当前状态: " + t1.getState());
            System.out.println("t2 当前状态: " + t2.getState());
    ​
            t2.start();
    ​
            // 等待 t2 尝试获取锁并进入 BLOCKED 状态
            Thread.sleep(200);
            System.out.println("t1 当前状态: " + t1.getState());
            System.out.println("t2 当前状态: " + t2.getState());
    ​
            // 等待 t1 释放锁,t2 进入 RUNNABLE 并执行
            Thread.sleep(3000);
            System.out.println("t1 当前状态: " + t1.getState());
            System.out.println("t2 当前状态: " + t2.getState());
    ​
            // 等待所有线程结束
            t1.join();
            t2.join();
    ​
            System.out.println("最终状态:");
            System.out.println("t1 最终状态: " + t1.getState());
            System.out.println("t2 最终状态: " + t2.getState());
    ​
        }
    }
    

image

4. WAITING(等待)

  • 定义:线程主动进入无限期等待,需其他线程显式唤醒。

  • 触发条件

    • 调用Object.wait()​、Thread.join()​或LockSupport.park()​。
  • 特点

    • 需通过notify()​、notifyAll()​或interrupt()​唤醒。
  • 示例

    public class WaitingExample {
        public static void main(String[] args) {
            Object lock = new Object();
            // 创建线程t1,进入等待状态
            Thread t1 = new Thread(() -> {
                synchronized (lock) {
                    try {
                        System.out.println("t1进入等待状态");
                        lock.wait(); // 进入WAITING状态
                        System.out.println("t1被唤醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t1.start();
    ​
            try {
                // 主线程让出CPU,确保t1先执行
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    ​
            // 创建线程t2,用于唤醒t1
            Thread t2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("t2唤醒t1");
                    lock.notify(); // 唤醒t1
                }
            });
            t2.start();
        }
    }
    

image

5. TIMED_WAITING(计时等待)

  • 定义:线程进入有限期等待,超时后自动恢复。

  • 触发条件

    • 调用Thread.sleep(long)​、Object.wait(long)​、Thread.join(long)​等带超时参数的方法。
  • 特点

    • 超时后自动返回RUNNABLE状态,或被其他线程唤醒。
  • 示例

    public class TimedWaitingExample {
        public static void main(String[] args) throws InterruptedException {
            // 线程t1:计时等待
            Thread t1 = new Thread(() -> {
                try {
                    System.out.println("t1: 开始计时等待(2秒)");
                    Thread.sleep(2000);       // 进入TIMED_WAITING状态
                    System.out.println("t1: 计时结束,自动恢复");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
    ​
            t1.start();
            Thread.sleep(500);  // 主线程短暂休眠,确保t1进入TIMED_WAITING
    ​
            // 展示t1的状态(实际开发中不需要频繁轮询)
            System.out.println("t1状态: " + t1.getState());  // 输出 TIMED_WAITING
    ​
            t1.join();  // 主线程等待t1执行完毕
            System.out.println("t1状态: " + t1.getState());  // 输出 TERMINATED
        }
    }
    

image

6. TERMINATED(终止)

  • 定义:线程运行结束或因异常退出。

  • 触发条件

    • run()​方法执行完毕。
    • 线程因未捕获异常终止。
  • 特点

    • 终止后不可再次启动。
  • 示例

    Thread t1 = new Thread(() -> System.out.println("线程结束"));
    t1.start();
    try {
        t1.join(); // 等待线程结束
    } catch (InterruptedException e) {}
    System.out.println(t1.getState()); // 输出: TERMINATED
    

三、线程生命周期的控制方法

在Java中,线程的生命周期由多种方法控制。以下是一些常用的方法及其作用:

1 start()

  • 启动线程并使线程进入“可运行状态”。
Thread thread = new Thread(() -> {
    // 执行任务
});
thread.start();  // 启动线程

2 sleep()

  • 使当前线程进入“超时等待状态”一段时间。
Thread.sleep(1000);  // 当前线程休眠 1 秒

3 join()

  • 当前线程调用另一个线程的 join()​ 方法时,当前线程会等待被调用线程完成后才继续执行。
Thread thread1 = new Thread(() -> {
    // 执行任务
});
Thread thread2 = new Thread(() -> {
    // 执行任务
});
thread1.start();
thread2.start();
thread1.join();  // 等待 thread1 执行完毕

4 wait()、notify()、notifyAll()

  • wait()​ 使当前线程进入等待状态。
  • notify()​ 唤醒一个正在等待的线程。
  • notifyAll()​ 唤醒所有等待的线程。
synchronized (lock) {
    lock.wait();  // 当前线程进入等待状态
    lock.notify();  // 唤醒一个等待线程
}

5. 线程的中断

线程的中断操作是通过 Thread.interrupt()​ 方法实现的。它不会直接终止线程,而是给线程发送一个中断信号,线程可以选择通过捕获中断异常来停止执行。

Thread thread = new Thread(() -> {
    while (!Thread.interrupted()) {
        // 执行任务
    }
});
thread.start();
thread.interrupt();  // 发出中断信号

、状态转换图

image

NEW
 ↓ start()
RUNNABLE ←→ BLOCKED/WAITING/TIMED_WAITING
 ↑ wait(), join(), sleep()        ↓ notify(), notifyAll(), interrupt(), timeout
TERMINATED ← [run()执行完毕或异常]

、关键状态区别与注意事项

1. RUNNABLE vs RUNNING

  • RUNNABLE:包含就绪和运行两种状态,由JVM统一表示。
  • RUNNING:线程实际占用CPU执行代码,但Java不单独定义此状态。

2. BLOCKED vs WAITING

状态触发条件资源释放
BLOCKED竞争锁失败(如synchronized​)不释放当前锁
WAITING主动调用wait()​、join()​等方法释放所有锁

3. 锁竞争与死锁

  • 锁竞争:多个线程争夺锁资源时,未成功获取的线程进入BLOCKED状态。
  • 死锁:线程A持有锁1并等待锁2,线程B持有锁2并等待锁1,导致双方永久阻塞。

六、总结

掌握Java线程的生命周期和状态是编写高效并发程序的关键。通过理解以下要点,开发者可以避免常见问题(如死锁、资源竞争),并优化程序性能:

  • 状态转换规则:从NEW到TERMINATED的完整流程。
  • 锁与等待的区别:BLOCKED与WAITING的触发条件和资源释放差异。
  • 调试工具:使用jstack​、VisualVM​分析线程状态,定位性能瓶颈。

“线程生命周期与状态的掌握,是每个 Java 程序员迈向高阶的重要一步。今天的文章只是为你揭开了这神秘领域的序幕。更多精彩深入的案例分析、实战技巧以及更多 Java 进阶知识,尽在我们的公众号 [技海拾贝]。

3ec794dacde34583aafafe15b1490012