• 通用线程模型

在很多研发当中,实际应用是基于一个理论再进行优化的。所以,在了解JVM规范中的Java线程的生命周期之前,我们可以先了解通用的线程生命周期,这有助于我们后续对JVM线程生命周期的理解。

首先,通用的线程生命周期有五种,分别是:新建状态(NEW)、可运行状态(RUNNABLE)、运行状态(RUN)、休眠状态(SLEEP)、终止状态(TERMINATED)。生命流程如下图所示:

  1. 新建状态(NEW)。线程在此状态,仅仅是在编程语言层面创建了此线程,而在真正的操作系统中是没有创建的。所以,它在这个状态下是无法获得CPU的执行的权限的。
  2. 可运行状态(RUNNABLE)。线程到达此状态,意味着它已经被操作系统创建,该线程获得被CPU执行的资格,但此时还没有被CPU执行相关操作。
  3. 运行状态(RUN)。线程获得CPU的执行权限,在一个特定的时间片内执行,线程仅在这个时间片内被称为运行状态。
  4. 休眠状态(SLEEP)。当线程调用了某个阻塞API或者等待IO操作的时候,它会释放当前CPU的执行权限,进入休眠状态。此时,线程没有获取CPU执行的资格,只有当该线程被唤醒时,线程才能进入RUNNABLE状态。
  5. 终止状态(TERMINATED)。当线程完成程序任务或者出现异常的时候,它就会进入终止状态。一个线程的使命就此结束。
  • JVM线程模型

JVM中的线程模型对于上面的通用线程模型进行了一些特有的分类和合并,它们的类别如下:

  1. 新建状态(NEW)
  2. 可运行/运行状态(RUNNABLE)
  3. 阻塞状态(BLOCK)
  4. 等待状态(WAITING)
  5. 有限等待状态(TIMED_WATING)
  6. 终止状态(TERMINATED)

而JVM中的状态结合到通用状态中可以如下图所示理解:

由上图可以看出,JVM讲运行中的线程和等待运行的线程归为一类,因为JVM不关心操作系统层面的调度,所以把这两个状态合并了。而Block、Wating、Timed_Wating三个状态在操作系统层面都为休眠状态没有区别。所以,这三种状态都没有获得CPU执行的资格

  • 线程状态的转换

从上面的JVM线程生命周期图分析,我来说说一个线程从新建到消亡的状态转变中,到底会发生什么事情。

  1. 从NEW到RUNNABLE

这个很简单,线程在被显式声明后,在调用start()方法前,这段时间都被称为NEW状态。如下代码所示

   Runnable task = ()-> System.out.println("线程启动");
//创建一个线程,线程状态为NEW状态
Thread thread = new Thread(task);

  2.从RUNNABLE到BLOCK

从RUNNABLE到BLOCK状态的转变只有一种途径,那就是在有synchronized关键字的程序当中。当线程执行到此,没有获取到synchronized的隐式锁,线程就会从RUNNABLE被阻塞为BLOCK状态。当阻塞中的线程获取到synchronized隐式锁时,它又会转变为RUNNABLE状态。

问:当线程调用阻塞API时,它的状态会不会改变呢? 例如我们日常说的:ServerSockt的accept()、Scanner的next()方法等。

答案是:对于JVM层面来说,调用这些方法的线程依旧在RUNNABLE状态,因为JVM对于等待CPU资源或等待IO资源并不关心,所以把他们归为RUNNABLE状态。而对于操作系统层面来说,线程则属于休眠状态。(对于较真的同学,可以通过jstack指令查看调用阻塞API是的线程是什么状态

  3.RUNNABLE到WATING

其中有三种场景会使线程转换为WATING状态:

  1. synchronized的内部,调用wait()方法。
  2. 调用Thread.join()方法。该方法的意思是,当一个A线程调用了B线程的join方法,那么A线程就必须等待B线程执行完毕,此时,A线程就为WATING状态。需要注意的是,如果是B线程自己调用自己的join方法。那么就会造成自己等待自己的局面,从而使线程无限等待。
  3. 调用LockSupport.park()方法。这个方法看上去很陌生,但是其实jdk中的并发包中的锁都是由它实现的。例如:我们日常中用到的lock.lock()方法,condition.await()方法,其底层都是通过调用这个方法运行的。

  4.RUNNABLE到TIMED_WATING状态

其实从字面上就可以看出,TIMED_WATING状态与WATING状态的差别就是TIMED_WATING会在有限的时间内等待。所以,在WATING方法中的大多数方法,只要加上一个时间参数,就会触发TIMED_WATING这个状态。具体的有:

         Thread.currentThread().join(millis);
Thread.sleep(millis);
Obj.wait(timeout);
LockSupport.parkNanos(Object blocker, long deadline);
LockSupport.parkUntil(long deadline);

  5.RUNNABLE到TERMINAL状态

当线程顺利的完成run()方法中的任务,就会进入TERMINAL状态。同时,当线程抛出没有处理异常的时候,线程同样会变为TERMINAL状态。那如果业务上需要我们主动的终止线程,那应该怎么做呢?

  • 终止线程的正确姿势

在以往的jdk中,它提供了一些诸如:stop()、suspend()、resume()方法,这些方法都会直接把线程关闭,不给线程任何处理的机会。这样做的风险可想而知,所以这些方法早就已经被标记为过时方法,不推荐使用,我也没有详细去了解。那么,我们现在想要终止一个线程,该怎么做呢?

答案就是:通过调用thread.interrupt();方法来达到终止线程的目的。当然,并不是调用interrupt()就会关闭线程,我们通过一个图来了解一下具体的流程是怎样的。

如图所示,线程状态的不同,对于Interrupt方法的处理也不同。流程在图中已经比较清晰,我再列出几个重点:

  1. Interrupt方法仅仅是把线程是否被中断的标识设置为true
  2. 当抛出InterruptedException时会把中断标志清除
  3. 被中断的线程状态不同,做出的响应也会不同。运行时线程需要主动检测、等待时的异常会抛出异常(这里可以类比硬件中的中断,相当于一个信号)。
  • 总结

我们在日常开发中,一旦遇到多线程的bug,分析线程dump信息是一个非常重要的手段。而了解线程运行时的状态,有助于在分析信息时正确的判断线程的状况。同样,我们可以通过jstack命令或者Java VisualVM可视化工具来查看线程的具体信息。

并发编程 || Java线程详解的更多相关文章

  1. 从缓存入门到并发编程三要素详解 Java中 volatile 、final 等关键字解析案例

    引入高速缓存概念 在计算机在执行程序时,以指令为单位来执行,每条指令都是在CPU中执行的,而执行指令过程中,势必涉及到数据的读取和写入. 由于程序运行过程中的临时数据是存放在主存(物理内存)当中的,这 ...

  2. Android并发编程之白话文详解Future,FutureTask和Callable

    从最简单的说起Thread和Runnable 说到并发编程,就一定是多个线程并发执行任务.那么并发编程的基础是什么呢?没错那就是Thread了.一个Thread可以执行一个Runnable类型的对象. ...

  3. 并发编程——IO模型详解

    ​ 我是一个Python技术小白,对于我而言,多任务处理一般就借助于多进程以及多线程的方式,在多任务处理中如果涉及到IO操作,则会接触到同步.异步.阻塞.非阻塞等相关概念,当然也是并发编程的基础. ​ ...

  4. java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  5. Java线程详解----借鉴

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  6. 【转】Java线程详解

    Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程 ...

  7. 并发编程——Java线程的6种状态及切换

    前言 本次主要分享一下Java线程的六种状态及其转换. 如果对于线程的创建方式不太了解,推荐观看并发编程--认识java里的线程 线程的状态及其转换 操作系统线程的五种状态 新建(NEW) 就绪(RU ...

  8. java线程详解(三)

    java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private R ...

  9. java线程——详解Callable、Future和FutureTask

    回顾: 接上篇博客 java线程--三种创建线程的方式,这篇博客主要介绍第三种方式Callable和Future.比较继承Thread类和实现Runnable接口,接口更加灵活,使用更广泛.但这两种方 ...

随机推荐

  1. Emu8086三种格式的代码-(顺序,分支,循环)

    这个学期准备考研,于是就没有怎么听别的课,现在临近期末,汇编成了个难题.下面是我学校的实验报告 做一个复习的记录吧,下面的代码都是在Emu8086上运行出来的代码 下面先介绍一下,汇编里面的格式问题 ...

  2. $O(k^2)$ 求前缀 $k$ 次幂和(与长度无关)

    接下来求解前缀幂次和 求解 \(\sum_{i = 1}^{k} i^k\) \[ \begin{aligned} (p+1)^k - 1 = (p+1)^k - p^k + p^k - (p-1)^ ...

  3. 数据结构——链栈(link stack)

    /* linkStack.c */ /* 链栈 */ #include <stdio.h> #include <stdlib.h> #include <stdbool.h ...

  4. [LeetCode] 901. Online Stock Span 股票价格跨度

    Write a class StockSpanner which collects daily price quotes for some stock, and returns the span of ...

  5. swagger Unable to render this definition

    Unable to render this definition The provided definition does not specify a valid version field. Ple ...

  6. Code Review最佳实践(转)

    我一直认为Code Review(代码审查)是软件开发中的最佳实践之一,可以有效提高整体代码质量,及时发现代码中可能存在的问题.包括像Google.微软这些公司,Code Review都是基本要求,代 ...

  7. springboot: xercesImpl.jar和xml-apis.jar (系统找不到指定的文件)

    springboot内置的tomcat为8.5.23, tomcat在8.5.2 中 修改了加载jar的方式,8.5.2 版本会解析jar中MANIFEST.MF文件,当该文件包含class-path ...

  8. Intellij Idea 自动更新资源和类

    Run -> Edit Configurations 进入配置页 On update action:当发现更新时的操作   选择Update classes and resources  On ...

  9. 如何打造难用,bug多的产品

    本文纯属吐槽,如有雷同,绝非巧合.长期更新,欢迎一起吐槽. 没有产品规划 需求方提出需求后,直接开发,无需经过产品规划,用开发的思维搞出来!于是我们得到了一堆功能的集合.这个集合可以让刚上手的新用户一 ...

  10. 【51nod1355】斐波那契的最小公倍数(min-max容斥)

    [51nod1355]斐波那契的最小公倍数(min-max容斥) 题面 51nod 题解 显然直接算还是没法算的,所以继续考虑\(min-max\)容斥计算. \[lcm(S)=\prod_{T\su ...