1.线程状态(生命周期)

一个线程在给定的时间点只能处于一种状态。

线程可以有如下6 种状态:

  • New (新创建):未启动的线程;
  • Runnable (可运行):可运行的线程,需要等待操作系统资源;
  • Blocked (被阻塞):等待监视器锁而被阻塞的线程;
  • Waiting (等待):等待唤醒状态,无限期地等待另一个线程唤醒;
  • Timed waiting (计时等待):在指定的等待时间内等待另一个线程执行操作的线程;
  • Terminated (被终止):已退出的线程。

要确定一个线程的当前状态, 可调用getState 方法

线程状态关系图

注意:虚线框(全大写英文)的状态为Java线程状态。

2.操作线程状态

2.1.新创建状态(NEW)

就是实例化线程完成后,未启动线程的状态。

可通过三种方式创建线程

  • 重写Thread类run()方法
  • 实现Runnable接口
  • 实现Callable接口

一个简单的例子概括三种方式

public class Demo {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
/**
* 1.直接重写run() 或继承Thread类再重写run()
*/
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("Thread");
}
};
// 开启线程
thread.start(); /**
* 2.lambda、内部类或线程类方式实现Runnable接口,实现run()方法
* 再交给Thread 类
*/
Thread runThread = new Thread(() -> {
System.out.println("Runnable");
});
// 开启线程
runThread.start(); /**
* 3.lambda、内部类或线程类方式实现Callable接口,实现call()方法
* 再交给Thread 类:FutureTask本质也是Runnable实现类
*/
FutureTask<String> futureTask = new FutureTask<String>(() -> {
System.out.println("Callable");
return "CallableThread";
});
Thread callThread = new Thread(futureTask);
// 开启线程
callThread.start();
// 获取call()方法的返回值
String s = futureTask.get();
System.out.println("call()方法的返回值:"+s);
} }

不重写 run() 或 call() 方法直接实例化Thread类创建的线程没有实际意义;

只有Callable方式创建的线程可以获取线程的返回值。

2.2.可运行状态(RUNNABLE)

该状态指的是线程实例化对象调用start()方法后进入的状态。线程处于可以运行状态,如果有处理器等资源,就可以执行程序。

该状态在操作系统层面包含两步:线程就绪和线程运行中,但在Java线程状态中,这两步都统称为Runnable(可运行)状态。

线程由就绪状态变为运行状态,重点就看你的线程有没有抢到CPU资源(CPU时间片),谁抢到就运行,没抢到就等。因为CPU时间片(执行时间)非常短,大概十几毫秒,所以线程切换的这个时间是非常短的,就绪状态变为运行状态的时间也非常短,在开发时几乎感觉不到这种状态的变化,所以在Java中将两者看作是一个整体,重点关注线程可否运行并区别于其他状态即可,更进一步简化线程的开发。如果你的程序要运行很久(比如写个死循环),在一个CPU时间片内没有执行完成,那么你的线程就要抢下一次的CPU时间片,抢到了才可以继续执行程序,没抢到那就要继续抢,直到线程中的程序执行完成。

其实这个场景应该都见到过,例如多个线程执行同一个程序,都将日志打印到同一个文件时,就会出现不同线程的日志混在了一起的情况,不利于排查问题。解决这种问题常见的方法有:一是分线程打印日志到不同文件;二是将日志信息保存到字符串对象中,在程序的最后将日志信息一次性打印到文件。第二种方式就是利用CPU的一个时间片来完成日志信息的打印。

注意:程序只能对新建状态的线程调用start()方法,不要对处于非新建状态的线程调用start() 方法,这都会引发IllegalThreadStateException异常。

2.3.被阻塞状态(BLOCKED)

线程处于等待监视器而被阻塞的状态。有一个线程获取了锁未释放,其他线程也来获取,但发现获取不到锁也进入了被阻塞状态。

被阻塞状态只存在于多线程并发访问下,区别于后面两种因线程自己进入”等待“而导致的阻塞。

进入状态

  • 进入synchronized 代码块/方法
  • 未获取到锁

退出状态

  • 获取到监视器锁

2.4.等待唤醒状态(WAITING)

整个流程是这样的:线程在某个对象的同步方法中先获取到对象锁;在执行wait方法时,该线程将释放对象锁,并且该线程被放入到这个对象的等待队列;等待另一个线程获取到同一个对象的锁,然后通过notify() 或 notifyAll() 方法唤醒对象等待队列中的线程。

从整个流程可以知道

wait (),notify () 和 notifyAll () 方法需要在线程获取到锁的情况下才可以继续执行,所以这三个方法都需要放在同步代码块/方法中执行,否则报异常:java.lang.IllegalMonitorStateException。

在同步代码块中,线程进入WAITING 状态时,锁会被释放,不会导致该线程阻塞。反过来想下,如果锁没释放,那其他线程就没办法获取锁,也就没办法唤醒它。

进入状态

  • object.wait()
  • thread.join()
  • LockSupport.park()

退出状态

  • object.notify()
  • object.notifyall()
  • LockSupport.unpark()

2.5.计时等待状态(TIMED_WAITING)

一般是计时结束就会自动唤醒线程继续执行后面的程序,对于Object.wait(long) 方法还可以主动通知唤醒。

注意:Thread类下的sleep() 方法可以放在任意地方执行;而wait(long) 方法和wait() 方法一样,需要放在同步代码块/方法中执行,否则报异常:java.lang.IllegalMonitorStateException。

进入状态

  • Thread.sleep(long)
  • Object.wait(long)
  • Thread.join(long)
  • LockSupport.parkNanos(long)
  • LockSupport.parkNanos(Object blocker, long nanos)
  • LockSupport.parkUntil(long)
  • LockSupport.parkUntil(Object blocker, long deadline)

注:blocker 参数为负责此线程驻留的同步对象。

退出状态

  • 计时结束
  • LockSupport.unpark(Thread)
  • object.notify()
  • object.notifyall()

2.6.终止(TERMINATED)

线程执行结束

  • run()/call() 执行完成
  • stop()线程
  • 错误或异常>>意外死亡

stop() 方法已弃用。

3.查看线程的6种状态

通过一个简单的例子来查看线程出现的6种状态。

案例

public class Demo3 {
private static Object object ="obj"; public static void main(String[] args) throws InterruptedException { Thread thread0 = new Thread(() -> {
try {
// 被阻塞状态(BLOCKED)
synchronized (object){
System.out.println("thread0 进入:等待唤醒状态(WAITING)");
object.wait();
System.out.println("thread0 被解除完成:等待唤醒状态(WAITING)");
}
System.out.println("thread0 "+Thread.currentThread().getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 新创建状态(NEW)
System.out.println(thread0.getName()+":"+thread0.getState()); Thread thread1 = new Thread(() -> {
try {
System.out.println("thread1 进入:计时等待状态(TIMED_WAITING)");
Thread.sleep(2);
System.out.println("thread1 出来:计时等待状态(TIMED_WAITING)");
} catch (InterruptedException e) {
e.printStackTrace();
}
// 被阻塞状态(BLOCKED)
synchronized (object){
System.out.println("thread1 解除:等待唤醒状态(WAITING)");
object.notify();
System.out.println("thread1 解除完成:等待唤醒状态(WAITING)");
}
System.out.println("thread1 "+Thread.currentThread().getState());
});
// 新创建状态(NEW)
System.out.println(thread1.getName()+":"+thread1.getState()); printState(thread0);
printState(thread1); // 可运行状态(RUNNABLE)
thread0.start();
// 可运行状态(RUNNABLE)
thread1.start(); } // 使用独立线程来打印线程状态
private static void printState(Thread thread) {
new Thread(()->{
while (true){
System.out.println(thread.getName()+":"+thread.getState());
if (thread.getState().equals(Thread.State.TERMINATED)){
System.out.println(thread.getName()+":"+thread.getState());
break;
}
}
}).start();
}
}

执行结果:简化后的输出结果

Thread-0:NEW

Thread-1:NEW

Thread-0:RUNNABLE

Thread-1:RUNNABLE

thread0 进入:等待唤醒状态(WAITING)

Thread-1:BLOCKED

thread1 进入:计时等待状态(TIMED_WAITING)

Thread-0:BLOCKED

Thread-0:WAITING

……

Thread-0:WAITING

Thread-1:BLOCKED

Thread-1:TIMED_WAITING

……

Thread-1:TIMED_WAITING

Thread-1:BLOCKED

……

Thread-1:BLOCKED

Thread-0:WAITING

……

Thread-0:WAITING

thread1 出来:计时等待状态(TIMED_WAITING)

Thread-0:WAITING

Thread-1:BLOCKED

thread1 解除:等待唤醒状态(WAITING)

Thread-1:BLOCKED

Thread-0:WAITING

Thread-0:BLOCKED

thread1 解除完成:等待唤醒状态(WAITING)

Thread-1:BLOCKED

thread1 RUNNABLE

Thread-0:BLOCKED

Thread-1:TERMINATED

thread0 被解除完成:等待唤醒状态(WAITING)

Thread-0:BLOCKED

thread0 RUNNABLE

Thread-0:TERMINATED

最终的执行结果如图。

注意:因为案例中使用了独立线程来打印不同线程的状态,会出现状态打印稍微延迟的情况。

自己编写平滑加权轮询算法,实现反向代理集群服务的平滑分配

Java实现平滑加权轮询算法--降权和提权

Java实现负载均衡算法--轮询和加权轮询

Java往期文章

Java全栈学习路线、学习资源和面试题一条龙

我心里优秀架构师是怎样的?

免费下载经典编程书籍

更多优质文章,请关注WX公众号:Java全栈布道师

Java线程状态(生命周期)--一篇入魂的更多相关文章

  1. Java线程的生命周期(转)

    Java线程的生命周期 一个线程的产生是从我们调用了start方法开始进入Runnable状态,即可以被调度运行状态,并没有真正开始运行,调度器可以将CPU分配给它,使线程进入Running状态,真正 ...

  2. 【Java并发基础】Java线程的生命周期

    前言 线程是操作系统中的一个概念,支持多线程的语言都是对OS中的线程进行了封装.要学好线程,就要搞清除它的生命周期,也就是生命周期各个节点的状态转换机制.不同的开发语言对操作系统中的线程进行了不同的封 ...

  3. java线程的生命周期及五种基本状态

    一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程各重要知识点.掌握了上图中的各知识点,Java中的多线程也就基本上掌 ...

  4. Java线程的生命周期

    线程的生命周期包括:新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死亡(Dead)5种状态.线程状态转换图如下: 1.新建状态(New) 当程序使用new关 ...

  5. 图解Java线程的生命周期,看完再也不怕面试官问了

    文章首发自个人微信公众号: 小哈学Java https://www.exception.site/java-concurrency/java-concurrency-thread-life-cycle ...

  6. Java—线程的生命周期及线程控制方法详解

    线程生命周期5种状态 介绍   线程的生命周期经过新建(New).就绪(Runnable).运行(Running).阻塞(Bolocked)和死亡(Dead) 状态转换图 新建(New)   程序使用 ...

  7. Java线程的生命周期与状态流转

    上图是一个线程的生命周期状态流转图,很清楚的描绘了一个线程从创建到终止的过程. 这些状态的枚举值都定义在java.lang.Thread.State下 NEW:毫无疑问表示的是刚创建的线程,还没有开始 ...

  8. Java线程之生命周期

    简述 以下类图展示了线程生命周期中不同的状态.我们可以创建一个线程并启动它,但是线程状态从Runnable.Running.Blocked等状态的变化取决于系统线程调度器,java本身并不能完全控制. ...

  9. Java 线程的生命周期

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(Running).阻塞(Blocked)和死 ...

  10. 深入Java线程管理(二):线程的生命周期

    Java线程的生命周期 一个线程的产生是从我们调用了start方法开始进入Runnable状态,即可以被调度运行状态,并没有真正开始运行,调度器可以将CPU分配给它,使线程进入Running状态,真正 ...

随机推荐

  1. 【动画进阶】神奇的 3D 卡片反光闪烁动效

    最近,有群里在群里发了这么一个非常有意思的卡片 Hover 动效,来源于此网站 -- key-drop,效果如下: 非常有意思酷炫的效果.而本文,我们不会完全还原此效果,而是基于此效果,尝试去制作这么 ...

  2. 【南大静态代码分析】作业 2:常量传播和 Worklist 求解器

    作业 2:常量传播和 Worklist 求解器 题目链接:https://tai-e.pascal-lab.net/pa2.html 评测链接:https://oj.pascal-lab.net/pr ...

  3. Angular系列教程之父子组件通信详解

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  4. Kubeadm 安装支持IPV6 K8S1.28.x的简单过程

    Kubeadm 安装支持IPV6 K8S的简单过程 背景 手贱 找了一个晚上想尝试安装一个K8S集群 并且可以支持IPV6 协议栈的 然后就开始各种百度. 各种处理 找到了一堆歪门邪道. 但是还不知道 ...

  5. Docker下的资源限制问题

    Docker下的资源限制问题 问题背景 公司某产品出现了一个奇怪的OOM 错误提示. 问题现象是 前台产品 提示 OOM cannot create native thread 但是同时查看 机器的资 ...

  6. [转帖]TiDB的使用限制

    https://docs.pingcap.com/zh/tidb/stable/tidb-limitations 本文会将详细描述 TiDB 中常见的使用限制,包括:标识符长度,最大支持的数据库.表. ...

  7. Jmeter学习之八_测试kafka

    Jmeter学习之八_测试kafka 背景 最近在持续学习. 昨天学习了grafana展示Jmeter测试数据库的结果 今天想着能够测试一下kafka验证一下kafka的吞吐量等信息 说干就干的. 遇 ...

  8. [转帖]redis 持久化方式 - aof 和 rdb 区别

    https://wenfh2020.com/2020/04/01/redis-persistence-diff/   aof 和 rdb 是 redis 持久化的两种方式.我们看看它们的特点和具体应用 ...

  9. [转帖]FIO磁盘性能测试工具

    https://www.cnblogs.com/lyhabc/p/16708771.html 简介 一般我们测试硬盘或者存储的性能的时候,会用Linux系统自带的dd命令,因为是自带命令,简单易使用, ...

  10. Chrome浏览器不同版本兼容性的验证方法

    Chrome浏览器不同版本兼容性的验证方法 背景 上周客户现场有出现使用国产信创设备上面的奇安信浏览器出现兼容性的问题. 开发认为是测试不全面导致. 认为测试应该必须测试过特定浏览器才可以进行说明. ...