并发编程 || Java线程详解

  • 通用线程模型

在很多研发当中,实际应用是基于一个理论再进行优化的。所以,在了解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状态。如下代码所示

1   Runnable task = ()-> System.out.println("线程启动");
2         //创建一个线程,线程状态为NEW状态
3         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这个状态。具体的有:

1         Thread.currentThread().join(millis);
2         Thread.sleep(millis);
3         Obj.wait(timeout);
4         LockSupport.parkNanos(Object blocker, long deadline);
5         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可视化工具来查看线程的具体信息。

原文地址:https://www.cnblogs.com/hill1126/p/11311600.html

时间: 2024-10-13 15:31:26

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

java线程详解

Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程.比如在Windows系统中,一个运行的exe就是一个进程. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.比如java.exe进程中可以运行很多线程.线程总是属于某个进程,进程中的多个线程共享进程的内存. “同时”执行是人的感觉,在线程之间实际上轮换执行. 二.Jav

Java线程详解----借鉴

Java线程:概念与原理 一.操作系统中线程和进程的概念 现在的操作系统是多任务操作系统.多线程是实现多任务的一种方式. 进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程.比如在Windows系统中,一个运行的exe就是一个进程. 线程是指进程中的一个执行流程,一个进程中可以运行多个线程.比如java.exe进程中可以运行很多线程.线程总是属于某个进程,进程中的多个线程共享进程的内存. “同时”执行是人的感觉,在线程之间实际上轮换执行. 二.Jav

Java线程详解(三)

Java线程:新特征-有返回值的线程 在Java5之前,线程是没有返回值的,常常为了"有"返回值,破费周折,而且代码很不好写.或者干脆绕过这道坎,走别的路了. 现在Java终于有可返回值的任务(也可以叫做线程)了. 可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口. 执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了. 下面是个很简单的例子: import jav

Java线程详解(二)

Java线程:新特征-线程池 线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源. 在使用线程池之前,必须知道如何去创建一个线程池,在Java5中,需要了解的是java.util.concurrent.Executors类的API,这个类提供大量创建连接池的静态方法,是必须掌握的. Java通过Executor

Java线程详解(一)

程序.进程.线程的概念  程序(program):是为完成特定任务.用某种语言编写的一组指令的集合.即指一段静态的代码,静态对象.  进程(process):是程序的一次执行过程,或是正在运行的一个程序.动态过程:有它自身的产生.存在和消亡的过程.     如:运行中的QQ,运行中的MP3播放器     程序是静态的,进程是动态的  线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径.     若一个程序可同一时间执行多个线程,就是支持多线程的 Java中多线程的创建和使

java 线程详解

5月7号  周末看了一下线程方面的内容 ,边看视频边看书还附带着参考了很多人的博客,一天的收获,写下来整理一下:感觉收获还是挺多的:过段时间可能看完java  这几大块要去看一下关于spring boot  的内容顺便  也整理一下:附上我参考的 几本书: 关于java  线程,首先要了解一下线程和进程之间的关系.区别以及他们之间的概念: 首先是线程: 什么是线程? 线程是在程序执行过程中能够执行部分代码的一个执行单元,也看看做是一个轻量级的进程:线程是程序内的程序控制流只能使用程序内分配给程序

java线程详解(三)

java线程间通信 首先看一段代码 class Res { String name; String sex; } class Input implements Runnable { private Res r; Input(Res r) { this.r = r; } public void run() { int x = 0; while(true){ if(x==0){ r.name = "mike"; r.sex = "male"; } else{ r.nam

java线程详解(二)

1,线程安全 先看上一节程序,我们稍微改动一下: //线程安全演示 //火车站有16张票,需要从四个窗口卖出,如果按照上面的多线程实现,程序如下 class Ticket implements Runnable { private int tick = 16;//票的张数---16 public void run(){ while(true){ if(tick>0){ //这里的sleep(100)是这次程序要表的关键,只是个模拟而已 try{ Thread.sleep(100); }catch

java线程详解(一)

1,相关概念简介 (1)进程:是一个正在执行的程序.每一个进程执行都有一个执行的顺序,该顺序就是一个执行路径,或者叫一个控制单元.用于分配空间. (2)线程:就是进程中一个独立的控制单元,线程在控制着进程的执行,一个进程中至少有一个线程. java虚拟机启动的时候会有一个进程java.exe,该进程中至少有一个线程在负责java程序的执行,这个线程运行的代码在main方法中,因此main方法是主线程.在更细节一点,java虚拟机不止一个线程,在启动main方法这个主线程时还有垃圾回收机制,其实这