线程是 JVM 执行任务的最小单元,理解线程的状态转换是理解后续多线程问题的基础。
在 JVM 运行中,线程一共有
NEW
、RUNNABLE
、BLOCKED
、WAITING
、TIMED_WAITING
、TERMINATED
六种状态,这些状态对应Thread.State
枚举类中的状态。
Thread.State
枚举源码:
为方便阅读,在此去掉了文档注释
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
在给定的时间点,线程只能处于这些状态中的一种状态。这些状态是不反映任何操作系统线程状态的虚拟机状态。
这两个状态比较好理解,当创建一个线程后,还没有调用start()
方法时,线程处在 NEW
状态,线程完成执行,退出后变为TERMINATED
终止状态。
运行
Thread
的start
方法后,线程进入RUNNABLE
可运行状态
/**
* 程序目的:观察线程的各种状态
* created at 2020-06-26 19:09
* @author lerry
*/
class MyThread extends Thread {
@Override
public void run() {
System.out.printf("%s线程运行\n", Thread.currentThread().getName());
}
}
/**
* 分别观察创建线程后、start()后、和线程退出后的线程状态。
* 其中Thread.sleep(50);是为了等待线程执行完
*/
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
System.out.printf("创建线程后,线程的状态为:%s\n", myThread.getState());
myThread.start();
System.out.printf("调用start()方法后线程的状态为:%s\n", myThread.getState());
// 休眠50毫秒,等待MyThread线程执行完
Thread.sleep(50);
System.out.printf("再次打印线程的状态为:%s\n", myThread.getState());
}
}
输出结果:
创建线程后,线程的状态为:NEW
调用start()方法后线程的状态为:RUNNABLE
Thread-0线程运行
再次打印线程的状态为:TERMINATED
我们可以看到,输出结果符合预期。
NEW
start()
方法后线程的状态变为:RUNNABLE
。run()
方法的执行,这个执行,是在主线程main
打印了调用start()方法后线程的状态为:RUNNABLE
输出后执行的。main
线程休眠了50毫秒,等待MyThread
线程退出MyThread
线程的状态,为TERMINATED
。如图左侧所示,在运行态中的线程进入
synchronized
同步块或者同步方法时,如果获取锁失败,则会进入到BLOCKED
状态。当获取到锁后,会从BLOCKED
状态恢复到就绪状态。
import lombok.extern.slf4j.Slf4j;
/**
* 程序目的:观察线程的BLOCKED状态
* created at 2020-06-26 19:09
* @author lerry
*/
@Slf4j
public class ThreadBlockedStateDemo {
public static void main(String[] args) {
Thread threadA = new Thread(() -> method01(), "A-Thread");
Thread threadB = new Thread(() -> method01(), "B-Thread");
threadA.start();
threadB.start();
log.info("线程A的状态为:{}", threadA.getState());
log.info("线程B的状态为:{}", threadB.getState());
}
/**
* 停顿10毫秒、模拟方法执行耗时
*/
public static synchronized void method01() {
log.info("[{}]:开始执行主线程的方法", Thread.currentThread().getName());
try {
Thread.sleep(10);
}
catch (InterruptedException e) {
e.printStackTrace();
}
log.info("[{}]:主线程的方法执行完毕", Thread.currentThread().getName());
}
}
输出结果:
2020-06-26 20:32:15.404 [A-Thread] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:开始执行主线程的方法
2020-06-26 20:32:15.404 [main ] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - 线程A的状态为:RUNNABLE
2020-06-26 20:32:15.407 [main ] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - 线程B的状态为:BLOCKED
2020-06-26 20:32:15.417 [A-Thread] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - [A-Thread]:主线程的方法执行完毕
2020-06-26 20:32:15.418 [B-Thread] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:开始执行主线程的方法
2020-06-26 20:32:15.430 [B-Thread] INFO com.hua.threadtest.state.ThreadBlockedStateDemo - [B-Thread]:主线程的方法执行完毕
A线程优先获得到了锁,状态为RUNNABLE
,这时,B线程处于BLOCKED
状态。
当A线程执行完毕后,B线程执行对应方法。
如图右侧所示,运行中的线程还会进入等待状态,这两个等待一个是有超时时间的等待,例如调用
Object.wait
、Thread.join
等;另外一个是无超时的等待,例如调用Thread.join
或者Locksupport.park
等。这两种等待都可以通过notify
或unpark
结束等待状态并恢复到就绪状态。
官方文档说明为:
A thread in the waiting state is waiting for another thread to perform a particular action. For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.
处于等待状态的线程正在等待另一个线程执行特定的操作。
接下来我们来模拟一下线程的WAITING
状态:
import lombok.extern.slf4j.Slf4j;
/**
*
* 程序目的:观察线程的WAITING状态
* 模拟:只有一个售票窗口的售票厅,有两个粉丝都想买票。
* 如果没有票,他们就继续等待、如果有票,则买票、然后离开售票厅。
* 其中,工作人员会补票,补票之后,粉丝就可以买到票了。
*
* created at 2020-06-26 19:09
* @author lerry
*/
@Slf4j
public class ThreadWaitingStateDemo {
public static void main(String[] args) throws InterruptedException {
Ticket ticket = new Ticket();
Thread threadA = new Thread(() -> {
synchronized (ticket) {
while (ticket.getNum() == 0) {
try {
ticket.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
ticket.buy();
}
}, "粉丝A");
Thread threadB = new Thread(() -> {
synchronized (ticket) {
while (ticket.getNum() == 0) {
try {
ticket.wait();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
ticket.buy();
}
}, "粉丝B");
threadA.start();
threadB.start();
// 确保A和B线程都运行起来
Thread.sleep(10);
log.info("粉丝A线程的状态为:{}", threadA.getState());
log.info("粉丝B线程的状态为:{}", threadB.getState());
Thread employeeThread = new Thread(() -> {
synchronized (ticket) {
if (ticket.getNum() == 0) {
ticket.addTickt();
ticket.notifyAll();
}
}
}, "补票员");
employeeThread.start();
}
}
@Slf4j
class Ticket {
/**
* 票的张数
*/
private int num = 0;
public int getNum() {
return num;
}
public void addTickt() {
try {
Thread.sleep(2_000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
log.info("补充票");
this.num += 2;
}
/**
* 停顿10毫秒、模拟方法执行耗时
*/
public void buy() {
log.info("[{}]:购买了一张票", Thread.currentThread().getName());
log.info("[{}]:退出售票厅", Thread.currentThread().getName());
}
}
输出:
2020-06-26 21:26:37.938 [main ] INFO com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:WAITING
2020-06-26 21:26:37.945 [main ] INFO com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:WAITING
2020-06-26 21:26:39.948 [补票员 ] INFO com.hua.threadtest.state.Ticket - 补充票
2020-06-26 21:26:39.949 [粉丝B ] INFO com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票
2020-06-26 21:26:39.949 [粉丝B ] INFO com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅
2020-06-26 21:26:39.949 [粉丝A ] INFO com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票
2020-06-26 21:26:39.949 [粉丝A ] INFO com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅
当修改ticket.wait();
为ticket.wait(10);
后,输出结果如下:
2020-06-26 21:27:10.704 [main ] INFO com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝A线程的状态为:TIMED_WAITING
2020-06-26 21:27:10.709 [main ] INFO com.hua.threadtest.state.ThreadWaitingStateDemo - 粉丝B线程的状态为:TIMED_WAITING
2020-06-26 21:27:12.714 [补票员 ] INFO com.hua.threadtest.state.Ticket - 补充票
2020-06-26 21:27:12.714 [粉丝B ] INFO com.hua.threadtest.state.Ticket - [粉丝B]:购买了一张票
2020-06-26 21:27:12.714 [粉丝B ] INFO com.hua.threadtest.state.Ticket - [粉丝B]:退出售票厅
2020-06-26 21:27:12.715 [粉丝A ] INFO com.hua.threadtest.state.Ticket - [粉丝A]:购买了一张票
2020-06-26 21:27:12.715 [粉丝A ] INFO com.hua.threadtest.state.Ticket - [粉丝A]:退出售票厅
wait()
放在while
循环的疑问为什么
ticket.wait();
要放在while (ticket.getNum() == 0)
代码块中呢?既然这行代码时让线程等待着,那使用if
不就行了?
我们设想一下,如果使用if
,则在线程被唤醒后,会继续往下执行,不再判断条件是否符合,这时还是没有票,粉丝也就购买不到票了。
我们看一下Object.wait()
的官方doc说明:
As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop:
synchronized (obj) {
while ()
obj.wait();
... // Perform action appropriate to condition
}
在一个参数版本中(wait方法),中断和虚假的唤醒是可能的,这个方法应该总是在循环中使用。
我们再继续看Object.wait(long timeout)
的文档说明:
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops
***
线程也可以在没有通知、中断或超时的情况下被唤醒,这就是所谓的假唤醒。虽然这种情况在实践中很少发生,但应用程序必须通过测试导致线程被唤醒的条件来防止这种情况发生,如果条件不满足,则继续等待。换句话说,等待应该总是在循环中发生
所以,为了避免很少发生
的假唤醒
出现时程序发生不可预知的错误,建议把wait()
调用放在循环语句中。这样就算被假唤醒
,也有条件语句的限制。
这也是为何wait
要放在循环语句中的一个原因。
表:处于等待
状态的各种细分状态对比
BLOCKED | WAITING | TIMED_WAITING | |
---|---|---|---|
何时会出现该状态 | 进入synchronized 同步块或者同步方法时,获取锁失败时 |
Object.wait with no timeout Thread.join with no timeout LockSupport.park |
Thread.sleep Object.wait with timeout Thread.join with timeout LockSupport.parkNanos LockSupport.parkUntil |
重新正常执行的条件 | 竞争到锁时 | Object.notify()、Object.notifyAll() | 等待时间结束、Object.notify()、Object.notifyAll() |
简单来说,处于BLOCKED
状态的线程,还是在竞争锁的,一旦cpu
有时间,它竞争到了锁、就会执行。
但是WAITING
状态的线程则不去竞争锁,需要等待被动通知、或者自己定的闹钟(等待时间)到了、再去竞争锁。
一图胜千言,在此引用一张国外一位大牛画的图:
图:线程转换-详细.jpg
Java 线程状态之 WAITING - Just do 挨踢 - OSCHINA
其中提到的排队上厕所的例子,让人忍俊不禁,作者举得例子太恰当了,寓教于乐,赞一个!
Java线程中wait状态和block状态的区别? - 知乎
Java 线程状态之 TIMED_WAITING - Just do 挨踢 - OSCHINA
java version "1.8.0_251"
Java(TM) SE Runtime Environment (build 1.8.0_251-b08)
Java HotSpot(TM) 64-Bit Server VM (build 25.251-b08, mixed mode)
macOS High Sierra 10.13.4
logback