线程状态及生长周期
介绍
在本文中,我们将详细讨论Java中的一个核心概念——线程的生命周期。
我们将使用一张图表帮大家理解,当然,还有一些实用的代码片段,以便更好地理解线程执行期间的这些状态。关于如何在Java中创建线程,我们可以参考这片文章开始。
Java中的多线程
在Java语言中,多线程是由线程的核心概念衍生出来的。在其生命周期中,线程会经历各种状态(如图):
线程的6个状态.png线程中的生命周期
在java.lang
包中,Thread
类包含一个静态枚举,它定义了线程可能存在的状态。在任何给定的时间点上,线程只能处于其中的一种状态:
-
NEW
: 新创建的尚未开始执行的线程 -
RUNNABLE
: 要么在运行,要么就是准备执行,但目前线程正处于资源分配(在等待着CPU为它分配执行时间)。 -
BLOCKED
: 等待获取monitor
锁或者尝试访问被其他线程锁住的代码片段时,就会处于该状态 -
WAITING
: 不受时间限制地等待其他线程执行某个特定操作 -
TIME_WAITING
: 等待其他线程在指定的时间内执行特定的操作 -
TERMINATED
: 已完成其执行
所有这些状态都包含在上面的图表中,现在让我们详细讨论其中的每一个。
NEW
NEW
表示已经创建但尚未启动的线程。在使用start()
方法启动它之前,它一直保持这种状态。
代码如下:
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
Log.info(t.getState());
因为我们还没有启动上面提到的线程,所以方法t.getState()
打印出来: NEW
。
Runnable
当我们创建了一个新线程并调用start()
方法时,它就从NEW
状态转移到了RUNNABLE
状态。处于这种状态的线程正在运行或准备运行(但它们正在等待系统分配资源)。
在多线程环境中,线程调度器(JVM
的一部分)为每个线程分配一定时间执行。因此,它会在运行一段特定的时间后,然后将控制权交给其他可运行的线程。
例如,让我们对之前的代码添加t.start()
方法,并尝试访问它的当前状态
Runnable runnable = new NewState();
Thread t = new Thread(runnable);
t.start();
Log.info(t.getState());
结果如下:
RUNNABLE
Blocked
当前线程并不满足运行的条件时,线程就会处于阻塞状态。通常线程在获取锁或者是访问synchronized
关键字修饰的方法或代码块(获取锁)时就会进入该状态。
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);
Log.info(t2.getState());
System.exit(0);
}
}
class DemoThreadB implements Runnable {
@Override
public void run() {
commonResource();
}
public static synchronized void commonResource() {
while(true) {
// Infinite loop to mimic heavy processing
// 't1' won't leave this method
// when 't2' try to enters this
}
}
}
以上代码:
- 我们创建了2个线程,
t1
和t2
. -
t1
启动并进入synchronized commonResource()
方法;这意味着只有一个线程可以访问它;试图访问此方法的其他后续线程将被阻塞,直到当前线程完成该方法的调用。 - 当
t1
进入该方法时,将会一直处于while
循环;这只是模拟繁重的处理,以便所有其他线程都不能进入此方法。 - 现在,当我们启动
t2
时,它试图进入commonResource()
方法,然而该方法已经被t1
访问,此时t2
将处于BLOCKED
状态。
Waiting
当线程等待其他线程执行特定操作时,它就处于Waiting
状态。根据Java官方文档,任何线程都可以通过调用以下三种方法中的任何一种来进入这种状态:
object.wait()
thread.join()
LockSupport.park()
public class WaitingState implements Runnable {
public static Thread t1;
public static void main(String[] args) {
t1 = new Thread(new WaitingState());
t1.start();
}
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);
}
}
}
class DemoThreadWS implements Runnable {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
Log.info(WaitingState.t1.getState());
}
}
Timed Waiting
当一个线程等待另一个线程在规定的时间内执行一个特定的动作时,它就处于TIMED_WAITING
状态。
根据Java官方文档,有五种方法可以将线程置于Timed Waiting
状态:
thread.sleep(long millis)
wait(int timeout) or wait(int timeout, int nanos)
thread.join(long millis)
LockSupport.parkNanos
LockSupport.parkUntil
要了解Java中wait()
和sleep()
的更多区别,请阅读这篇专门的文章。
现在,让我们试着快速重现这个状态
public class TimedWaitingState {
public static void main(String[] args) throws InterruptedException {
DemoThread obj1 = new DemoThread();
Thread t1 = new Thread(obj1);
t1.start();
// The following sleep will give enough time for ThreadScheduler
// to start processing of thread t1
Thread.sleep(1000);
Log.info(t1.getState());
}
}
class DemoThread implements Runnable {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Log.error("Thread interrupted", e);
}
}
}
这里,我们已经创建并启动了一个线程t1
,该线程进入5s
睡眠状态。
输出结果:
TIMED_WAITING
Terminated
当线程完成执行或异常终止时,线程就会处于Terminated
状态。
我们有一篇专门讨论停止线程的不同方法的文章。
让我们在下面的示例中尝试实现这种状态:
public class TerminatedState implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new TerminatedState());
t1.start();
// The following sleep method will give enough time for
// thread t1 to complete
Thread.sleep(1000);
Log.info(t1.getState());
}
@Override
public void run() {
// No processing in this block
}
}
这里,虽然我们已经启动了线程t1
,但是thread .sleep(1000)
给了t1
足够的时间来完成,所以这个程序最终给我们的输出的结果是:
TERMINATED
以上就是线程状态及线程生命周期的讨论。
Waiting 原理(如图)
wait原理.png- 如果有多个线程开始抢锁时,这些线程都会进入
entry set
集合,等待锁的获取。 - 一旦有线程获取锁后再次执行
wait
方法时,该线程则会对当前锁进行释放,并进入waiting
状态。 - 直到有线程唤醒,唤醒后的线程再次开始抢锁,直到再次获取到锁。
线程转态转换的特殊情况
- 从
object.wait()
状态刚被唤醒时,通常不能立刻抢到monitor
锁,那就会从Waiting
先进入到Blocked
状态,抢到锁后在转换到Runnable
状态(官方文档)。 - 如果发生异常,可以直接跳到终止
Terminated
状态,不必再遵循路径,比如可以从Waiting
直接跳到Terminated
。