前言

本文描述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. 防止xss攻击的前端的方法

    项目当中在进行安全测试的时候,遇到了xss的攻击,要求前端来做个防御,针对于遇到的xss攻击,做个总结 1.xss---存储型xss的攻击 前端只要在接收到后台数据的时候做个特殊字符的过滤,即可抵制攻 ...

  2. 3.2 Spark运行架构

    一.基本概念 1.RDD Resillient Distributed Dataset 弹性分布式数据集 2.DAG 反映RDD之间的依赖关系 3.Executor 进程驻守在机器上面,由进程派生出很 ...

  3. css,区别pc端ipad端的样式

    摘自: http://blog.csdn.net/pm_mybook/article/details/54602107 /* 横屏 */ @media all and (orientation:lan ...

  4. 201871010101-陈来弟《面向对象程序设计(java)》第十六周学习总结

    实验十四  应用程序归档与线程初步 实验时间 2019-12-12 第一部分:基础知识 1. 程序与进程: 进程是指一个具有一定独立功能的程序关于某个数据集合的一次运行活动.电脑中时会有很多单独运行的 ...

  5. angular 学习记录

    3章3小结 路由传参的3种方式和路由快照,订阅, @相同路由的跳转(只是参数不同),并不会触发Oninit ,因为没有重新创建component @子路由 //此种情况 是当我路由地址是 ../Hom ...

  6. 微信小程序 - 组件 | 自定义组件 | 组件事件传递页面

    组件 小程序允许我们使用自定义组件的方式来构建页面 类似Vue的小组件 自定义组件 类似于页面,一个自定义组件由 json, wxml, wxss, js 4个文件组成 1.创建 1.创建compon ...

  7. 洛谷 U86501 趣味擂台

    洛谷 U86501 趣味擂台 题目传送门 题目背景 \(JDFZ\)\(2019\)秋季运动会开始辣!运动会中有一个叫做"趣味擂台"的游戏...... 题目描述 游戏内容是这样的: ...

  8. JDOJ 2254 Who am I?

    JDOJ 2254: Who am I? Description 输出程序自己本身的源代码. Input 无 Output 输出程序自己本身的源代码. 我真是搞不懂了出这道题还把它归到程序语法基础题里 ...

  9. yum源加速,替换为阿里云镜像

    问题 使用yum命令安装mysql时,发现下载速度很慢,于是决定换成阿里的yum源 解决方法 参考自:https://www.jianshu.com/p/b7cd2f9fb8b7 首先备份一下原先的y ...

  10. CSP2019 Emiya 家今天的饭 题解

    这题在考场上只会O(n^3 m),拿了84分.. 先讲84分,考虑容斥,用总方案减去不合法方案,也就是枚举每一种食材,求用它做超过\(\lfloor \frac{k}{2} \rfloor\) 道菜的 ...