Java 线程生命周期:一段精彩的旅程
各位看官,大家好!今天咱们来聊聊Java线程这个神秘又重要的家伙。线程,在Java的世界里,就像辛勤的小蜜蜂,嗡嗡嗡地忙碌着,执行着我们交给它们的任务。但蜜蜂也有生老病死,线程也一样,它们的一生并非一帆风顺,而是经历着各种状态的切换。今天,咱们就来扒一扒Java线程的生命周期,看看它们是如何从呱呱坠地的新生儿,一步步走向光荣退休的。
线程的五大状态:人生的五个阶段
Java线程的生命周期,可以被简化为五个主要状态:
- 新建 (New):就像刚出生的婴儿,拥有了生命,但还没开始活动。
- 就绪 (Runnable):婴儿长大了一些,可以爬可以走了,等待着被选中去执行任务。
- 运行 (Running):终于被选中了!开始执行任务,就像婴儿开始探索世界,学习新事物。
- 阻塞 (Blocked/Waiting/Timed Waiting):遇到了障碍,需要等待,就像婴儿饿了要等妈妈喂奶,困了要睡觉。
- 死亡 (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
指的是线程正在执行。 由于操作系统层面的线程调度非常快,很多时候我们难以区分 READY
和 RUNNING
这两个细微的状态。
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线程的生命周期就像一段精彩的人生旅程,从 NEW
到 TERMINATED
,经历了各种各样的状态。 了解线程的状态转换,可以帮助我们更好地理解多线程编程,编写出更高效、更稳定的并发程序。
希望这篇文章能够帮助大家更好地理解Java线程的生命周期。 如果大家有任何问题,欢迎留言讨论。 谢谢大家!