这么说线程生命周期,是否是简单了点?

点击上方码农沉思录,选择“设为星标”html

优质文章,及时送达java


现陆续将Demo代码和技术文章整理在一块儿 Github实践精选 ,方便你们阅读查看,本文一样收录在此,以为不错,还请Star🌟git

为何要了解线程的生命周期?github

以前写过 Spring Bean 生命周期三部曲:面试

有朋友留言说:“了解了它们的生命周期后,使用 Spring Bean 比如看到它们的行动轨迹,如今使用就一点都不慌了”。我和他同样,了解事物的生命周期目的很简单,惟【不慌】也编程


Java 并发系列 已经写了不少,历来还没提起过那个它【Java线程生命周期】。有了前序理论图文的铺垫,在走进源码世界以前,谈论它的时机刚好到了。由于,编写并发程序的核心之一就是正确的摆弄线程状态微信

线程生命周期的几种状态并发

刚接触线程生命周期时,我老是记不住,也理解不了他们的状态,能够说是比较混乱,更别说它们之间是如何进行状态转换的了。缘由是我把操做系统通用线程状态和编程语言封装后的线程状态 概念混淆在一块儿了编程语言

操做系统通用线程状态ide

我的以为通用线程状态更符合咱们的思考习惯。其状态总共有 5 种 (以下图)。对于常常写并发程序的同窗来讲,其嘴里常常念的都是操做系统中的这些通用线程状态,且看

除去生【初始状态】死【终止状态】,其实只是三种状态的各类转换,听到这句话是否是心情放松了不少呢?

为了更好的说明通用线程状态和 Java 语言中的线程状态,这里仍是先对前者进行简短的说明

初始状态

线程已被建立,可是还不被容许分配CPU执行。注意,这个被建立实际上是属于编程语言层面的,实际在操做系统里,真正的线程还没被建立, 好比 Java 语言中的 new Thread()。

可运行状态

线程能够分配CPU执行,这时,操做系统中线程已经被建立成功了

运行状态

操做系统会为处在可运行状态的线程分配CPU时间片,被 CPU 临幸后,处在可运行状态的线程就会变为运行状态

休眠状态

若是处在运行状态的线程调用某个阻塞的API或等待某个事件条件可用,那么线程就会转换到休眠状态,注意:此时线程会释放CPU使用权,休眠的线程永远没有机会得到CPU使用权,只有当等待事件出现后,线程会从休眠状态转换到可运行状态

终止状态

线程执行完或者出现异常 (被interrupt那种不算的哈,后续会说)就会进入终止状态,正式走到生命的尽头,没有起死回生的机会

接下来就来看看你熟悉又陌生,面试又常常被问到的Java 线程生命周期吧

Java语言线程状态

在 Thread 的源码中,定义了一个枚举类 State,里面清晰明了的写了Java语言中线程的6种状态:

  1. NEW

  2. RUNNABLE

  3. BLOCKED

  4. WAITING

  5. TIMED_WAITING

  6. TERMINATED


这里要作一个小调查了,你有查看过这个类和读过其注释说明吗?(欢迎留言脚印哦)


耳边响起五环之歌,Java中线程状态居然比通用线程状态的 5 种多1种,变成了 6 种。这个看似复杂,其实并非你想的那样,Java在通用线程状态的基础上,有裁剪,也有丰富,总体来讲是少一种。再来看个图,注意颜色区分哦


Java 语言中

将通用线程状态的可运行状态和运行状态合并为 Runnable,

将休眠状态细分为三种 (BLOCKED/WAITING/TIMED_WAITING); 反过来理解这句话,就是这三种状态在操做系统的眼中都是休眠状态,一样不会得到CPU使用权

看上图右侧【Java语言中的线程状态】,进一步简洁的说,除去线程生死,咱们只要玩转 RUNNABLE 和休眠状态的转换就能够了,编写并发程序也多数是这两种状态的转换。因此咱们须要了解,有哪些时机,会触发这些状态转换

远看看轮廓, 近看看细节。咱们将上面Java语言中的图进行细化,将触发的节点放到图中 (这看似复杂的图,其实三句话就能分解的,因此别慌),且看:


RUNNABLE与BLOCKED状态转换

当且仅有(just only)一种状况会从 RUNNABLE 状态进入到 BLOCKED 状态,就是线程在等待 synchronized 内置隐式锁;若是等待的线程获取到了 synchronized 内置隐式锁,也就会从 BLOCKED 状态变为 RUNNABLE 状态了

注意:上面提到,以操做系统通用状态来看,线程调用阻塞式 API,会变为休眠状态(释放CPU使用权),但在JVM层面,Java线程状态不会发生变化,也就是说Java线程的状态依旧会保持在 RUNNABLE 状态。JVM并不关心操做系统调度的状态。在JVM看来,等待CPU使用权(操做系统里是处在可执行状态)与等待I/O(操做系统是处在休眠状态),都是等待某个资源,因此都纳入了RUNNABLE 状态—— 摘自《Java并发编程实战》

RUNNABLE与WAITING状态转换

调用不带时间参数的等待API,就会从RUNNABLE状态进入到WAITING状态;当被唤醒就会从WAITING进入RUNNABLE状态

RUNNABLE与 TIMED-WAITING 状态转换

调用带时间参数的等待API,天然就从 RUNNABLE 状态进入 TIMED-WAITING 状态;当被唤醒或超时时间到就会从TIMED_WAITING进入RUNNABLE状态

看图中的转换 API 挺多的,其实不用担忧,后续分析源码章节,天然就会记住的,如今有个印象以及知道状态转换的节点就行了

相信到这里,你看Java线程生命周期的眼神就没那么迷惑了,重点就是RUNNABLE与休眠状态的切换,接下来咱们看一看,如何查看线程中的状态,以及具体的代码触发点

如何查看线程处在什么状态程序中调用 getState() 方法

Thread 类中一样存在  getState() 方法用于查看当前线程状态,该方法就是返回上面提到的枚举类 State


NEW

就是上面提到, 编程语言中特有的,经过继承 Thread 或实现 Runnable 接口定义线程后,这时的状态都是 NEW

Thread thread = new Thread(() -> {});System.out.println(thread.getState());RUNNABLE

调用了 start() 方法以后,线程就处在 RUNNABLE 状态了

Thread thread = new Thread(() -> {});thread.start();//Thread.sleep(1000);System.out.println(thread.getState());BLOCKED等待 synchronized 内置锁,就会处在 BLOCKED 状态public class ThreadStateTest {
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);
System.out.println((t2.getState())); System.exit(0); }}
class DemoThreadB implements Runnable { @Override public void run() { commonResource(); }
public static synchronized void commonResource() { while(true) {
} }}WAITING调用线程的 join() 等方法,从 RUNNABLE 变为 WAITING 状态public static void main(String[] args) throws InterruptedException { Thread main = Thread.currentThread();
Thread thread2 = new Thread(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); e.printStackTrace(); } System.out.println(main.getState()); }); thread2.start(); thread2.join(); }TIMED-WAITING

调用了 sleep(long) 等方法,线程从 RUNNABLE 变为 TIMED-WAITING 状态

public static void main(String[] args) throws InterruptedException { Thread thread3 = new Thread(() -> { try { Thread.sleep(3000); } catch (InterruptedException e) { // 为何要调用interrupt方法? Thread.currentThread().interrupt(); e.printStackTrace(); } }); thread3.start();
Thread.sleep(1000); System.out.println(thread3.getState()); }TERMINATED线程执行完天然就到了 TERMINATED 状态了Thread thread = new Thread(() -> {});thread.start();Thread.sleep(1000);System.out.println(thread.getState());

以上是程序中查看线程,本身写写测试看看状态还好,现实中的程序怎么可能容许你加这么多无用代码,因此,翠花,上酸菜(jstack



jstack 命令查看

相信你据说过这玩意,jstack 命令就比较强大了,不只能查看线程当前状态,还能看调用栈,锁等线程栈信息

你们能够随意写一些程序,这里我用了上面 WAITING 状态的代码, 修改睡眠时间 Thread.sleep(100000),而后在终端按照下图标示依次执行下图命令


更多功能还请你们自行查看,后续会单独写文章来教你们如何使用jstack查看线程栈信息

Arthas

这个利器,无须多言吧,线上找茬监控没毛病,但愿你能够灵活使用这个工具,攻克疑难杂症


查看线程栈详细信息,很是方便:https://alibaba.github.io/arthas/thread.html


相信你已经和Arthas确认了眼神


关于线程生命周期状态总体就算说完了,编写并发程序时多问一问本身:

调用某个API会将你的线程置为甚么状态?

多问本身几回,天然就记住上面的图了

灵魂追问

  1. 为何调用 Thread.sleep, catch异常后,调用了Thread.currentThread().interrupt();

  2. 进入 BLOCKED只有一种状况,就是等待 synchronized 监视器锁,那调用 JUC 中的 Lock.lock() 方法,若是某个线程等待这个锁,这个线程状态是什么呢?为何?

public class ThreadStateTest {
public static void main(String[] args) throws InterruptedException { TestLock testLock = new TestLock();
Thread thread2 = new Thread(() -> { testLock.myTestLock(); }, "thread2");
Thread thread1 = new Thread(() -> { testLock.myTestLock(); }, "thread1");
thread1.start(); Thread.sleep(1000);
thread2.start(); Thread.sleep(1000);
System.out.println("****" + (thread2.getState()));
Thread.sleep(20000); }}
@Slf4jclass TestLock{ private final Lock lock = new ReentrantLock();
public void myTestLock(){ lock.lock(); try{ Thread.sleep(10000); log.info("testLock status"); } catch (InterruptedException e) { log.error(e.getMessage()); } finally { lock.unlock(); } }}

参考

感谢前辈们总结的精华,本身所写的并发系列好多都参考了如下资料

Java 并发编程实战

Java 并发编程之美

码出高效

Java 并发编程的艺术

......

本文分享自微信公众号 - 码农沉思录(code-thinker)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。

相关文章
相关标签/搜索