Java 线程生命周期:新建、就绪、运行、阻塞与死亡状态

Java 线程生命周期:一段精彩的旅程

各位看官,大家好!今天咱们来聊聊Java线程这个神秘又重要的家伙。线程,在Java的世界里,就像辛勤的小蜜蜂,嗡嗡嗡地忙碌着,执行着我们交给它们的任务。但蜜蜂也有生老病死,线程也一样,它们的一生并非一帆风顺,而是经历着各种状态的切换。今天,咱们就来扒一扒Java线程的生命周期,看看它们是如何从呱呱坠地的新生儿,一步步走向光荣退休的。

线程的五大状态:人生的五个阶段

Java线程的生命周期,可以被简化为五个主要状态:

  1. 新建 (New):就像刚出生的婴儿,拥有了生命,但还没开始活动。
  2. 就绪 (Runnable):婴儿长大了一些,可以爬可以走了,等待着被选中去执行任务。
  3. 运行 (Running):终于被选中了!开始执行任务,就像婴儿开始探索世界,学习新事物。
  4. 阻塞 (Blocked/Waiting/Timed Waiting):遇到了障碍,需要等待,就像婴儿饿了要等妈妈喂奶,困了要睡觉。
  5. 死亡 (Terminated):任务完成或者遇到了不可抗拒的因素,线程结束生命,就像人终有一死。

可以用一张表格来概括一下:

状态 描述 触发条件
New 线程被创建,但尚未启动。 Thread thread = new Thread(runnable);
Runnable 线程已准备好运行,正在等待JVM的调度。包括 Ready(就绪)和 Running(运行)两种状态,只是 Running 的状态正在执行。 thread.start();
Running 线程正在执行。 JVM 线程调度器选中了该线程。
Blocked 线程被阻塞,等待监视器锁。 synchronized 关键字,尝试进入被其他线程占用的同步块。
Waiting 线程进入等待状态,等待其他线程的特定操作。 Object.wait(), Thread.join(), LockSupport.park()
Timed Waiting 线程进入带超时时间的等待状态,等待特定操作或超时。 Thread.sleep(), Object.wait(timeout), Thread.join(timeout), LockSupport.parkNanos(), LockSupport.parkUntil()
Terminated 线程已完成执行。 线程的 run() 方法执行完毕,或者因为异常而终止。

接下来,咱们逐一深入了解这些状态,并用代码示例来加深理解。

1. 新建 (New):线程的诞生

线程的创建,就像生命的孕育。当我们使用 new Thread() 创建一个线程对象时,它就进入了新建状态。此时,线程只是一个空壳,还没有分配任何系统资源,更没有开始执行任何代码。

public class NewThreadExample {

    public static void main(String[] args) {
        // 创建一个Runnable对象
        Runnable runnable = () -> {
            System.out.println("线程正在运行...");
        };

        // 创建一个线程对象,但线程还未启动
        Thread thread = new Thread(runnable);

        // 此时,线程处于 NEW 状态
        System.out.println("线程状态: " + thread.getState()); // 输出: 线程状态: NEW
    }
}

在这个例子中,我们创建了一个 Thread 对象,并传入了一个 Runnable 对象,但我们并没有调用 start() 方法。因此,线程仍然处于 NEW 状态。

2. 就绪 (Runnable):等待被宠幸

当我们调用 start() 方法时,线程就从 NEW 状态进入了 RUNNABLE 状态。RUNNABLE 状态并非意味着线程立即开始执行,而是表示线程已经准备好了,可以被JVM调度器选中去执行。

RUNNABLE 状态其实包含了两种状态:READY (就绪) 和 RUNNING (运行)。 READY 指的是线程已经准备好,等待被调度器选中,而 RUNNING 指的是线程正在执行。 由于操作系统层面的线程调度非常快,很多时候我们难以区分 READYRUNNING 这两个细微的状态。

public class RunnableThreadExample {

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println("线程正在运行...");
            try {
                Thread.sleep(1000); // 模拟执行耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程运行结束.");
        };

        Thread thread = new Thread(runnable);
        thread.start(); // 线程进入 RUNNABLE 状态

        System.out.println("线程状态: " + thread.getState()); // 输出可能是 RUNNABLE 或 RUNNING,取决于线程是否已经被调度器选中

        try {
            Thread.sleep(50); // 让主线程稍微等待一下,以便子线程有机会运行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程状态: " + thread.getState()); // 输出可能是 RUNNABLE 或 TERMINATED,取决于线程是否已经执行完毕

        try {
            thread.join(); // 等待线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("线程状态: " + thread.getState()); // 输出: 线程状态: TERMINATED
    }
}

在这个例子中,我们调用了 thread.start() 方法,线程进入 RUNNABLE 状态。 但是,System.out.println("线程状态: " + thread.getState()); 这行代码的输出可能是 RUNNABLE 也可能是 RUNNING,这取决于线程是否已经被JVM调度器选中去执行。

3. 运行 (Running):开始表演

当线程被JVM调度器选中时,它就进入了 RUNNING 状态。 此时,线程开始执行 run() 方法中的代码,真正开始干活了。

public class RunningThreadExample {

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println("线程正在运行...");
            for (int i = 0; i < 5; i++) {
                System.out.println("线程正在执行第 " + i + " 次循环.");
                try {
                    Thread.sleep(500); // 模拟耗时操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程运行结束.");
        };

        Thread thread = new Thread(runnable);
        thread.start();

        // 主线程也执行一些任务
        for (int i = 0; i < 3; i++) {
            System.out.println("主线程正在执行第 " + i + " 次循环.");
            try {
                Thread.sleep(700); // 模拟耗时操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        try {
            thread.join(); // 等待子线程执行完毕
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("主线程运行结束.");
    }
}

在这个例子中,主线程和子线程并发执行,你可以看到它们交替输出,这就是多线程的魅力。

4. 阻塞 (Blocked/Waiting/Timed Waiting):人生不如意十之八九

线程在执行过程中,可能会遇到各种各样的阻碍,例如:

  • 获取锁失败: 线程尝试进入一个被其他线程占用的 synchronized 代码块或方法。
  • 等待其他线程的通知: 线程调用了 Object.wait() 方法,进入等待状态,等待其他线程调用 Object.notify()Object.notifyAll() 方法唤醒它。
  • 等待其他线程完成: 线程调用了 Thread.join() 方法,等待其他线程执行完毕。
  • 睡眠: 线程调用了 Thread.sleep() 方法,进入睡眠状态,等待指定的时间后自动醒来。
  • LockSupport.park(): 线程调用了 LockSupport.park() 方法,进入等待状态,直到其他线程调用 LockSupport.unpark()

根据等待原因的不同,阻塞状态又可以细分为:

  • Blocked: 等待获取锁。
  • Waiting: 无期限等待,需要被其他线程显式唤醒。
  • Timed Waiting: 有期限等待,等待指定的时间后自动醒来。

4.1 Blocked 状态:锁,锁,锁

当线程试图进入被其他线程 synchronized 锁保护的代码块时,如果锁已经被占用,那么该线程就会进入 BLOCKED 状态。

public class BlockedThreadExample {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取到锁.");
                try {
                    Thread.sleep(5000); // 模拟长时间占用锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1释放锁.");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取到锁.");
            }
        });

        thread1.start();
        Thread.sleep(100); // 确保线程1先获取到锁
        thread2.start();

        Thread.sleep(100);

        System.out.println("线程1状态: " + thread1.getState()); // 输出: 线程1状态: TIMED_WAITING (因为sleep)
        System.out.println("线程2状态: " + thread2.getState()); // 输出: 线程2状态: BLOCKED (等待线程1释放锁)

        thread1.join();
        thread2.join();
    }
}

在这个例子中,线程1先获取了锁,并睡眠了5秒钟。线程2试图获取同一个锁,但由于锁已经被线程1占用,所以线程2进入了 BLOCKED 状态。

4.2 Waiting 状态:漫长的等待

当线程调用 Object.wait()Thread.join()LockSupport.park() 方法时,它就会进入 WAITING 状态,等待其他线程的特定操作。

  • Object.wait(): 必须在 synchronized 代码块或方法中调用,线程会释放锁,并进入等待队列,等待其他线程调用 Object.notify()Object.notifyAll() 方法唤醒它。
  • Thread.join(): 等待指定线程执行完毕。
  • LockSupport.park(): 阻塞当前线程,直到 LockSupport.unpark() 被调用,或者被中断。
public class WaitingThreadExample {

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1获取到锁,准备等待...");
                try {
                    lock.wait(); // 线程1进入 WAITING 状态,并释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1被唤醒,继续执行.");
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程2获取到锁,准备唤醒线程1...");
                try {
                    Thread.sleep(2000); // 模拟一些操作
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                lock.notify(); // 唤醒等待队列中的一个线程 (线程1)
                System.out.println("线程2唤醒了线程1.");
            }
        });

        thread1.start();
        Thread.sleep(100); // 确保线程1先进入等待状态
        thread2.start();

        thread1.join();
        thread2.join();
    }
}

在这个例子中,线程1先获取了锁,然后调用 lock.wait() 方法进入 WAITING 状态,并释放了锁。线程2随后获取了锁,并调用 lock.notify() 方法唤醒了线程1。

4.3 Timed Waiting 状态:有期限的等待

当线程调用 Thread.sleep()Object.wait(timeout)Thread.join(timeout)LockSupport.parkNanos()LockSupport.parkUntil() 方法时,它就会进入 TIMED_WAITING 状态。 与 WAITING 状态不同的是,TIMED_WAITING 状态有一个超时时间,当等待时间超过指定时间后,线程会自动醒来。

public class TimedWaitingThreadExample {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程开始睡眠...");
            try {
                Thread.sleep(3000); // 线程进入 TIMED_WAITING 状态,等待3秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程睡眠结束,继续执行.");
        });

        thread.start();

        Thread.sleep(100); // 确保线程进入睡眠状态

        System.out.println("线程状态: " + thread.getState()); // 输出: 线程状态: TIMED_WAITING

        thread.join();
    }
}

在这个例子中,线程调用 Thread.sleep(3000) 方法,进入 TIMED_WAITING 状态,等待3秒钟。

5. 死亡 (Terminated):生命的终结

当线程执行完 run() 方法中的所有代码,或者因为发生了未捕获的异常而导致线程终止时,线程就进入了 TERMINATED 状态。 一旦线程进入 TERMINATED 状态,它就无法再次启动。

public class TerminatedThreadExample {

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            System.out.println("线程正在运行...");
            for (int i = 0; i < 3; i++) {
                System.out.println("线程正在执行第 " + i + " 次循环.");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程运行结束.");
        });

        thread.start();
        thread.join(); // 等待线程执行完毕

        System.out.println("线程状态: " + thread.getState()); // 输出: 线程状态: TERMINATED
    }
}

在这个例子中,线程执行完 run() 方法中的循环后,自动结束,进入 TERMINATED 状态。

线程状态转换图

为了更清晰地理解线程状态之间的转换关系,我们可以用一张图来表示:

stateDiagram
    [*] --> New : 创建线程
    New --> Runnable : 调用 start() 方法
    Runnable --> Running : 被 JVM 调度器选中
    Running --> Runnable : 时间片用完,让出 CPU
    Running --> Blocked : 尝试获取锁,但锁被占用
    Running --> Waiting : 调用 wait(), join(), park()
    Running --> TimedWaiting : 调用 sleep(), wait(timeout), join(timeout), parkNanos(), parkUntil()
    Running --> Terminated : run() 方法执行完毕或发生未捕获的异常
    Blocked --> Runnable : 获取到锁
    Waiting --> Runnable : 被 notify(), notifyAll() 唤醒 或 unpark()
    TimedWaiting --> Runnable : 超时时间到达 或 被 interrupt() 中断

总结

Java线程的生命周期就像一段精彩的人生旅程,从 NEWTERMINATED,经历了各种各样的状态。 了解线程的状态转换,可以帮助我们更好地理解多线程编程,编写出更高效、更稳定的并发程序。

希望这篇文章能够帮助大家更好地理解Java线程的生命周期。 如果大家有任何问题,欢迎留言讨论。 谢谢大家!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注