前言

本文描述Java线程线程状态及状态转换,不会涉及过多理论,主要以代码示例说明线程状态如何转换。

基础知识

1. 线程状态

Thread源码中的状态说明:

线程可以有6种状态:

  • New(新建)
  • Runnable(可运行)
  • Blocked(被阻塞)
  • Waiting(等待)
  • Timed waiting(计时等待)
  • Terminated(被终止)
  1. New:new Thread()后线程的状态就是新建。
  2. Runnable:线程一旦调用start()方法,无论是否运行,状态都为Runable,注意Runable状态指示表示线程可以运行,不表示线程当下一定在运行,线程是否运行由虚拟机所在操作系统调度决定。
  3. 被阻塞:线程试图获取一个内部对象的Monitor(进入synchronized方法或synchronized块)但是其他线程已经抢先获取,那此线程被阻塞,知道其他线程释放Monitor并且线程调度器允许当前线程获取到Monitor,此线程就恢复到可运行状态。
  4. 等待:当一个线程等待另一个线程通知调度器一个条件时,线程进入等待状态。
  5. 计时等待:和等待类似,某些造成等待的方法会允许传入超时参数,这类方法会造成计时等待,收到其他线程的通知或者超时都会恢复到可运行状态。
  6. 被终止:线程执行完毕正常结束或执行过程中因未捕获异常意外终止都会是线程进入被终止状态。

2. 线程状态转换

线程从“新建”到“被终止”会历经多次状态转换,所有可能的转换如下图:

【图一】

观察状态转化图,我们发现“可运行”状态为所有状态的必经状态。我们分析出四条基本的状态转换线路图。

  • 新建--->可运行--->被终止
  • 新建--->可运行--->被阻塞--->可运行--->被终止
  • 新建--->可运行--->等待--->可运行--->被终止
  • 新建--->可运行--->计时等待--->可运行--->被终止

“新建”和“被终止”状态分别为起始和结束状态,和“可运行”状态不可逆。其他状态均能和“可运行”状态相互转换。

用代码说话

让我们用代码演示线程状态是如何转换的,大家重点关注两个问题?

  • 什么操作会改变线程状态?
  • 改变的状态是如何恢复的?

一、 新建--->可运行--->被终止

这个状态转换时创建的线程生命周期。

/**
* NEW->RUNNABLE->TERMINATED
*/
public class ThreadStateNRT { public static void main(String[] args) {
Thread thread=new Thread(new Task());
print(thread.getName(),thread.getState());
thread.start();
//等待线程执行完毕。
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
print(thread.getName(),thread.getState());
} private static class Task implements Runnable{
@Override
public void run() {
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
private static final String stringFormat="%s:%s";
private static void print(String threadName,Thread.State state){
System.out.println(String.format(stringFormat,threadName,state));
}
}

其中,print()方法用来打印线程信息。后面的代码示例均不在展示。

运行程序结果为:

Thread-0:NEW 
Thread-0:RUNNABLE 
Thread-0:TERMINATED

二、 新建--->可运行--->被阻塞--->可运行--->被终止

只有一种方法能出现阻塞状态,那就是synchronized同步原语。我们需要两个线程其中一个线程被另一个阻塞来展示。

/**
* NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED
*/
public class ThreadStateNRBRT {
//锁
private static final Object lock=new Object(); public static void main(String[] args) {
//辅助线程,制造synchronized状态。
Thread assistantThread = new Thread(new SynTask());
assistantThread.start();
try {
//保证assistantThread先执行。
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread showThread = new Thread(new Task());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(),showThread.getState());
//因为无法判断显示线程何时执行,所以循环直到显示线程执行。
while (true){
if(showThread.getState()==Thread.State.BLOCKED){
print(showThread.getName(), Thread.State.BLOCKED);
break;
}
}
//等待两个线程执行完毕。
try {
assistantThread.join();
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class SynTask implements Runnable {
@Override
public void run() {
//锁定一定时间
synchronized (lock){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} private static class Task implements Runnable {
@Override
public void run() {
synchronized (lock){
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
}
}

执行一下你有可能看到正确结果:

Thread-1:NEW
Thread-1:RUNNABLE
Thread-1:BLOCKED
Thread-1:RUNNABLE
Thread-1:TERMINATED

为什么是有可能呢?我们调整一下代码,例如将加锁的时间调小一点:

private static class SynTask implements Runnable {
@Override
public void run() {
//锁定一定时间
synchronized (lock){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

注意此处Thread.sleep(10),我们只锁住十毫秒。

再运行一下,控制台可能打印出这样的结果且程序不会结束:

Thread-1:NEW
Thread-1:RUNNABLE
Thread-1:RUNNABLE

造成以上结果的原因是我么无法保证两个线程的执行顺序,也无法证主线程一定能打印出显示线程阻塞的状态。

         while (true){
if(showThread.getState()==Thread.State.BLOCKED){
print(showThread.getName(), Thread.State.BLOCKED);
break;
}
}

所以执行在这段代码死循环了。

调整一下代码,保证不会因为参数调整改变线程之间的执行顺序和打印结果。

/**
* NEW->RUNNABLE->BLOCKED->RUNNABLE->TERMINATED
*/
public class ThreadStateNRBRT_New {
//锁
private static final Object lock=new Object();
//锁定标志
private volatile static boolean lockFlag=true;
//执行顺序
private volatile static int order=0; public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new Task());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(), showThread.getState());
//辅助线程,制造synchronized状态。
Thread assistantThread = new Thread(new SynTask());
assistantThread.start(); //循环读取展示线程状态,直到读到展示线程状态为BLOCKED,才让辅助线程退出同步。
while (true){
if(showThread.getState()==Thread.State.BLOCKED){
print(showThread.getName(), Thread.State.BLOCKED);
lockFlag=false;
break;
}
} try {
assistantThread.join();
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class SynTask implements Runnable {
@Override
public void run() {
while (true) {
//保证先进入同步范围。
if (order == 0) {
synchronized (lock) {
//启动另一个同步
order=1;
//等待主线程读取到线程阻塞状态,退出同步。
while (lockFlag) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
break;
}
}
}
} private static class Task implements Runnable {
@Override
public void run() {
while (true){
//保证后进入同步范围。
if (order==1){
synchronized (lock){
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
break;
}
}
}
}
}

我们用order保证线程进入同步区的顺序,用lockFlag保证只有在打印出显示线程的被阻塞状态后辅助线程才退出同步区。这样无论如何执行我们都会得到同样的结果。

Thread-0:NEW
Thread-0:RUNNABLE
Thread-0:BLOCKED
Thread-0:RUNNABLE
Thread-0:TERMINATED

三、 新建--->可运行--->等待--->可运行--->被终止

这里我们展示两种三种方法造成线程的等待状态

  • Object.wait()
  • java.util.concurrent.locks.Locke.lock()
  • java.util.concurrent.locks.Condition.await()

其他方法如Thread.join()等大家可以参考示例代码自己实现。

1. Object.wait()

/**
* NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED
*/
public class ThreadStateNRWRT {
//锁
private static final Object lock=new Object(); public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new WaitTask());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(),showThread.getState());
//循环读取展示线程状态,直到读到展示线程状态为WAITING,才让辅助线程唤醒等待线程。
while (true){
if(showThread.getState()==Thread.State.WAITING){
print(showThread.getName(), Thread.State.WAITING);
break;
}
}
synchronized (lock){
lock.notify();
} try {
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class WaitTask implements Runnable {
@Override
public void run() {
//等待
synchronized (lock){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
}

2. java.util.concurrent.locks.Locke.lock()

/**
* NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED
*/
public class ThreadStateNRWRTLock {
//锁
private static Lock lock=new ReentrantLock();
//锁定标志
private volatile static boolean lockFlag=true;
//执行顺序
private volatile static int order=0; public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new Task());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(), showThread.getState());
//辅助线程,制造synchronized状态。
Thread assistantThread = new Thread(new SynTask());
assistantThread.start();
//循环读取展示线程状态,直到读到展示线程状态为BLOCKED,才让辅助线程退出同步。
while (true){
if(showThread.getState()==Thread.State.WAITING){
print(showThread.getName(), Thread.State.WAITING);
lockFlag=false;
break;
}
}
try {
assistantThread.join();
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class SynTask implements Runnable {
@Override
public void run() {
while (true) {
//保证先进入同步范围。
if (order == 0) {
//加锁
lock.lock();
try {
order=1;
while (lockFlag) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
break;
}
}
}
} private static class Task implements Runnable {
@Override
public void run() {
while (true){
//保证后进入同步范围。
if (order==1){
lock.lock();
try{
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}finally {
lock.unlock();
}
break;
}
}
}
}
}

3. java.util.concurrent.locks.Condition.await()

/**
* NEW->RUNNABLE->WAITING->RUNNABLE->TERMINATED
*/
public class ThreadStateNRWRTCondition {
//锁
private static Lock lock=new ReentrantLock();
private static Condition condition=lock.newCondition(); public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new WaitTask());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(),showThread.getState());
//循环读取展示线程状态,直到读到展示线程状态为WAITING,才让辅助线程唤醒等待线程。
while (true){
if(showThread.getState()==Thread.State.WAITING){
print(showThread.getName(), Thread.State.WAITING);
break;
}
} lock.lock();
try{
condition.signal();
}finally {
lock.unlock();
} try {
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class WaitTask implements Runnable {
@Override
public void run() {
//等待
lock.lock();
try{
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
}

4. 运行结果

三段代码的运行结果都是:

Thread-0:NEW
Thread-0:RUNNABLE
Thread-0:WAITING
Thread-0:RUNNABLE
Thread-0:TERMINATED

四、新建--->可运行--->计时等待--->可运行--->被终止

我们展示两个方法造成计时等待状态

  • Object.wait(long timeout)
  • java.util.concurrent.locks.Condition.await(long time, TimeUnit unit)

其他方法如Thread.sleep(long millis),Thread.join(long millis)等大家可以自己实现。
感觉凡是有超时方法的方法都能让线程状态进入计时等待,但是这个没有经过验证,所以只是一个猜想。

1. Object.wait(long timeout)

/**
* NEW->RUNNABLE->TIMED_WAITING->RUNNABLE->TERMINATED
*/
public class ThreadStateNRTWRT {
//锁
private static final Object lock=new Object(); public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new WaitTask());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(),showThread.getState());
//循环读取展示线程状态,直到读到展示线程状态为TIMED_WAITING。
while (true){
if(showThread.getState()==Thread.State.TIMED_WAITING){
print(showThread.getName(), Thread.State.TIMED_WAITING);
break;
}
} try {
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class WaitTask implements Runnable {
@Override
public void run() {
//等待
synchronized (lock){
try {
lock.wait(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
}

2. java.util.concurrent.locks.Condition.await(long time, TimeUnit unit)

/**
* NEW->RUNNABLE->TIMED_WAITING->RUNNABLE->TERMINATED
*/
public class ThreadStateNRTWRTCondition {
//锁
private static Lock lock=new ReentrantLock();
private static Condition condition=lock.newCondition(); public static void main(String[] args) {
//展示线程
Thread showThread = new Thread(new WaitTask());
print(showThread.getName(), showThread.getState());
showThread.start();
print(showThread.getName(),showThread.getState());
//循环读取展示线程状态,直到读到展示线程状态为TIMED_WAITING。
while (true){
if(Thread.State.TIMED_WAITING==showThread.getState()){
print(showThread.getName(), Thread.State.TIMED_WAITING);
break;
}
}
try {
showThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//线程执行完毕打印状态。
print(showThread.getName(), showThread.getState());
} private static class WaitTask implements Runnable {
@Override
public void run() {
//等待
lock.lock();
try{
try {
condition.await(1,TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
print(Thread.currentThread().getName(),Thread.currentThread().getState());
}
}
}

3. 运行结果

两段程序的运行结果相同:

Thread-0:NEW
Thread-0:RUNNABLE
Thread-0:TIMED_WAITING
Thread-0:RUNNABLE
Thread-0:TERMINATED

结语

至此,我们已经介绍了线程状态转换的所有情况,了解线程状态转换对分析多线程代码运行很帮助。希望本篇文章对大家今后工作有所助力。

参考

《Java核心技术+卷1》第九版

转自:https://segmentfault.com/a/1190000016197831?utm_source=tag-newest

一文读懂Java线程状态转换的更多相关文章

  1. Java线程状态转换

    前言:对于Java线程状态方面的知识点,笔者总感觉朦朦胧胧,趁着最近整理资料,将Java线程状态方面的知识点总结归纳,以便加深记忆. 1.Java线程状态值 在Thread类源码中通过枚举为线程定义了 ...

  2. 浅谈 Java线程状态转换及控制

    线程的状态(系统层面) 一个线程被创建后就进入了线程的生命周期.在线程的生命周期中,共包括新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)这五 ...

  3. 一文读懂Java动态代理

    作者 :潘潘 日期 :2020-11-22 事实上,对于很多Java编程人员来说,可能只需要达到从入门到上手的编程水准,就能很好的完成大部分研发工作.除非自己强主动获取,或者工作倒逼你学习,否则我们好 ...

  4. 一文读懂JAVA多线程

    背景渊源 摩尔定律 提到多线程好多书上都会提到摩尔定律,它是由英特尔创始人之一Gordon Moore提出来的.其内容为:当价格不变时,集成电路上可容纳的元器件的数目,约每隔18-24个月便会增加一倍 ...

  5. 一文搞懂 Java 线程中断

    在之前的一文<如何"优雅"地终止一个线程>中详细说明了 stop 终止线程的坏处及如何优雅地终止线程,那么还有别的可以终止线程的方法吗?答案是肯定的,它就是我们今天要分 ...

  6. 一文读懂Java中的动态代理

    从代理模式说起 回顾前文: 设计模式系列之代理模式(Proxy Pattern) 要读懂动态代理,应从代理模式说起.而实现代理模式,常见有下面两种实现: (1) 代理类关联目标对象,实现目标对象实现的 ...

  7. JAVA 线程状态转换图示及说明

    线程状态类型 新建状态(New):新创建了一个线程对象. 就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中,变得可运行,等待获取C ...

  8. 别指望一文读懂Java并发之从一个线程开始

    Understanding concurrent programming is on the same order of difficulty as understanding object-orie ...

  9. [No0000196]一文读懂Java 11的ZGC为何如此高效

    导读:GC是大部分现代语言内置的特性,Java 11 新加入的ZGC号称可以达到10ms 以下的 GC 停顿,本文作者对这一新功能进行了深入解析.同时还对还对这一新功能带来的其他可能性做了展望.ZGC ...

随机推荐

  1. [Linux] nginx的try_files指令实现隐藏index.php的重写

    1.nginx的try_files指令 ,核心功能是替代rewrite,并且比rewrite更强大的是可以按顺序查找文件是否存在,如果文件都找不到才会执行最后的重定向解决的问题是,如果一个网站的部署是 ...

  2. flask的request如何获取参数

    1.request.form.get("key", type=str, default=None) 获取表单数据 2.request.args.get("key" ...

  3. SHELL脚本--变量

    环境变量 环境变量就是运行在"环境"上下文的,在这个上下文都可以引用.例如,常见的cd.ls等命令严格来说应该使用绝对路径如/bin/ls来执行,由于/bin目录加入到了PATH环 ...

  4. mac os下切换pip3国内源并安装requests库

    在使用Python的时候,经常会用到pip来安装模块,但是默认的下载源实在是特别慢,经常install的时候还会因为速度的原因直接报错,因此我们可以选择将下载源更改为国内的,这样就可以提高我们的下载速 ...

  5. 03-cmake语法-变量,字符串

    CMake的基本数据类型是字符串(不区分大小写),一组字符串在一起称为列表(list). 条件判断中的取值情况如下表: 真 1, ON, YES, TRUE, Y, 非0的数  假 0, OFF, N ...

  6. java修饰符的总结

    引言:Java的修饰符根据修饰的对象不同,分为类修饰符.方法修饰符.变量修饰符,其中每种修饰符又分为访问控制修饰符和非访问控制修饰符.访问控制存在的原因:a.让客户端程序员无法触及他们不应该触及的部分 ...

  7. plantUML 安装

    plantUML 安装 资源 http://www.graphviz.org/ https://graphviz.gitlab.io/_pages/Download/windows/graphviz- ...

  8. BOI 2003 团伙

    洛谷 P1892 [BOI2003]团伙 洛谷传送门 题目描述 1920年的芝加哥,出现了一群强盗.如果两个强盗遇上了,那么他们要么是朋友,要么是敌人.而且有一点是肯定的,就是: 我朋友的朋友是我的朋 ...

  9. 原生js拖拽、jQuery拖拽、vue自定义指令拖拽

    原生js拖拽: <!DOCTYPE html> <html lang="en"> <head> <meta charset="U ...

  10. 洛谷p3384【模板】树链剖分题解

    洛谷p3384 [模板]树链剖分错误记录 首先感谢\(lfd\)在课上调了出来\(Orz\) \(1\).以后少写全局变量 \(2\).线段树递归的时候最好把左右区间一起传 \(3\).写\(dfs\ ...