1.介绍
详细讨论 Java 中的一个核心概念——线程的生命周期。
2.Java 中的多线程
在 Java 语言中,多线程是由线程的核心概念驱动的。 在它们的生命周期中,线程会经历各种状态:
3.Java 线程的生命周期
java.lang.Thread 类包含一个静态的 State 枚举——它定义了它的潜在状态。 在任何给定的时间点,线程只能处于以下状态之一:
- NEW – 新创建的尚未开始执行的线程
- RUNNABLE – 正在运行或准备执行但正在等待资源分配
- BLOCKED – 等待获取监视器锁以进入或重新进入同步块/方法
- WAITING – 等待其他线程执行特定操作而没有任何时间限制
- TIMED_WAITING – 等待某个其他线程在指定时间段内执行特定操作
- TERMINATED – 已完成执行
所有这些状态都包含在上图中;
3.1. 新建
新线程(或出生线程)是已创建但尚未启动的线程。 它一直保持这种状态,直到使用 start() 方法启动它。
以下代码片段显示了一个新创建的处于 NEW 状态的线程:
@Test
public void test1(){
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
System.out.println(t.getState());
}
private static class NewState implements Runnable {
@Override
public void run() {
System.out.println("NewState Thread ...");
}
}
由于还没有启动提到的线程,方法 t.getState() 打印:
NEW
3.2. 运行
当创建一个新线程并对其调用 start() 方法时,它就会从 NEW 状态变为 RUNNABLE 状态。 处于此状态的线程正在运行或准备运行,但它们正在等待系统分配资源。
在多线程环境中,线程调度器(它是 JVM 的一部分)为每个线程分配固定的时间量。 因此它会运行一段特定的时间,然后将控制权交给其他 RUNNABLE 线程。
例如,在之前的代码中添加 t.start() 方法并尝试访问其当前状态:
@Test
public void test2(){
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
System.out.println(t.getState());
}
此代码最有可能将输出返回为:
RUNNABLE
请注意,在此示例中,并不总是当调用 t.getState() 时,处于 RUNNABLE 状态。
它可能会被线程调度器立即调度并可能完成执行。 在这种情况下,可能会得到不同的输出。
3.3. 阻塞
当线程当前没有资格运行时,它处于 BLOCKED 状态。 它在等待监视器锁并尝试访问被其他线程锁定的代码段时进入此状态。
public class BlockedState {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new DemoThreadB());
Thread t2 = new Thread(new DemoThreadB());
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(t2.getState());
System.exit(0);
}
}
class DemoThreadB implements Runnable {
@Override
public void run() {
commonResource();
}
public static synchronized void commonResource() {
while (true) {
// 模拟繁重处理的无限循环 't1' 不会在 't2' 尝试进入此方法时离开此方法
}
}
}
在这段代码中:
- 创建了两个不同的线程——t1 和 t2
- t1 启动并进入同步的 commonResource() 方法; 这意味着只有一个线程可以访问它; 尝试访问此方法的所有其他后续线程将被阻止进一步执行,直到当前线程完成处理
- 当t1进入该方法时,一直处于无限while循环中; 这只是为了模仿繁重的处理,以便所有其他线程无法进入此方法
- 现在当启动 t2 时,它试图进入 commonResource() 方法,该方法已经被 t1 访问,因此,t2 将保持在 BLOCKED 状态
在这种状态下,调用 t2.getState() 并获得输出
BLOCKED
3.4. 等待
线程在等待某个其他线程执行特定操作时处于 WAITING 状态。 根据 JavaDocs,任何线程都可以通过调用以下三种方法中的任何一种来进入这种状态:
- object.wait()
- thread.join()
- LockSupport.park()
请注意,在 wait() 和 join() 中,没有定义任何超时
重现这种状态:
public class WaitingState implements Runnable {
public static Thread t1;
public static void main(String[] args) {
t1 = new Thread(new WaitingState());
t1.start();
}
@Override
public void run() {
Thread t2 = new Thread(new DemoThreadWS());
t2.start();
try {
t2.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Thread interrupted", e);
}
}
}
@Slf4j
class DemoThreadWS implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("Thread interrupted", e);
}
System.out.println(WaitingState.t1.getState());
}
}
如上代码:
- 创建并启动了 t1
- t1 创建一个 t2 并启动它
- 当 t2 的处理继续时,调用 t2.join(),这将 t1 置于 WAITING 状态,直到 t2 完成执行
- 由于 t1 正在等待 t2 完成,从 t2 调用 t1.getState()
这里的输出是:
WAITING
3.5.定时等待
一个线程在等待另一个线程在规定的时间内执行特定操作时处于 TIMED_WAITING 状态。
根据 JavaDocs,有五种方法可以将线程置于 TIMED_WAITING 状态:
- thread.sleep(long millis)
- wait(int timeout)或wait(int timeout, int nanos)
- thread.join(long millis)
- LockSupport.parkNanos
- LockSupport.parkUntil
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
DemoThread obj1 = new DemoThread();
Thread t1 = new Thread(obj1);
t1.start();
Thread.sleep(1000);
System.out.println(t1.getState());
}
}
class DemoThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
在这里,创建并启动了一个线程 t1,该线程进入睡眠状态,超时时间为 5 秒; 输出将是:
TIMED_WAITING
3.6. 已终止
这是死线程的状态。 当它完成执行或异常终止时,它处于 TERMINATED 状态。
public class TerminatedState implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TerminatedState());
t1.start();
// 下面的 sleep 方法会给线程 t1 足够的时间来完成
Thread.sleep(1000);
System.out.println(t1.getState());
}
@Override
public void run() {
}
}
在这里,虽然已经启动了线程 t1,但接下来的语句 Thread.sleep(1000) 为 t1 提供了足够的时间来完成,因此输出如下:
TERMINATED
除了线程状态,还可以通过 isAlive() 方法来判断线程是否存活。 例如,在这个线程上调用 isAlive() 方法:
Assert.assertFalse(t1.isAlive());
返回false。 简而言之,当且仅当一个线程已经启动并且还没有死亡时,它才是存活的。