• 通用线程模型

在很多研发当中,实际应用是基于一个理论再进行优化的。所以,在了解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. Shell编程——环境变量

    在Shell程序启动时会自动定义一组变量,这组变量就是环境变量,系统中的所有命令都可以使用这些变量参数. 1.如果在父Shell定义环境变量,在子Shell中也能查看到. (1)父Shell与子She ...

  2. Codeforces Round #551 (Div. 2) D 树形dp

    https://codeforces.com/contest/1153/problem/D 题意 一颗有n个点的数,每个点a[i]为0代表取子节点的最小,1代表取最大,然后假设树上存在k个叶子,问你如 ...

  3. [LeetCode] 5. Longest Palindromic Substring 最长回文子串

    Given a string s, find the longest palindromic substring in s. You may assume that the maximum lengt ...

  4. 【洛谷】P4594 [COCI2011-2012#5] BLOKOVI

    本来已经有一个专门记录洛谷题目的博客了,但这个题之毒瘤...... 为你专门写一篇总行了吧...... 传送门 先说一句,这个题每次摆放都靠到最右边不一定是最优的 因为它可以这个亚子 就是说上面那个块 ...

  5. 第01组 Beta冲刺(4/5)

    队名:007 组长博客: https://www.cnblogs.com/Linrrui/p/12019233.html 作业博客: https://edu.cnblogs.com/campus/fz ...

  6. Java一个简单的重试工具包

    在接口调用中由于各种原因,可能会重置失败的任务,使用Guava-Retrying可以方便的实现重试功能. 首先,需要引用Guava-Retrying的包 <dependency> < ...

  7. Git恢复之前版本的两种方法reset、revert(图文详解)(转)

    一.问题描述在利用github实现多人合作程序开发的过程中,我们有时会出现错误提交的情况,此时我们希望能撤销提交操作,让程序回到提交前的样子,本文总结了两种解决方法:回退(reset).反做(reve ...

  8. 使用JDBC连接MySQL数据库操作增删改查

    目录 1.首先这个Myeclipse的包名以及一些实现的类(这样子写是我的习惯) 2.接下来我们创建数据库(MySQL) 3.在数据库里面添加数据 4.首先是BaseDao,这个是重中之重,注意那个数 ...

  9. eclipse打开本地文件所在目录位置的快捷键

    在开发的过程中总是会遇到需要在本地文件夹找到该本地文件的情况,比如说要发送给同事什么的. 在使用Eclipse的过程中,大多数人都是先在Eclipse目录中定位到文件,然后通过在文件的右键属性中找到文 ...

  10. AtCoder Grand Contest 014

    AtCoder Grand Contest 014 A - Cookie Exchanges 有三个人,分别有\(A,B,C\)块饼干,每次每个人都会把自己的饼干分成相等的两份然后给其他两个人.当其中 ...