目录
一、操作系统中进程和线程的状态
1.1 进程
1.1.1 进程的概念
1.1.2 进程的状态
1.1.3 进程调度流程图(五状态)
1.1.4 挂起状态
1.1.4 进程调度流程图(六状态和七状态)
1.1.5 睡眠状态
1.1.6 进程的诞生与消亡
1.2 线程
1.2.1 线程的概念
1.2.2 线程与进程的比较
1.2.3 线程的状态
1.2.4 线程调度流程图
1.2.5 线程的内核调度
1.2.6 线程的诞生与消亡
二、Java中的线程
2.1 Java 线程的状态
2.2 操作系统中的线程状态
2.3 Java线程的状态流转图
2.4 验证Java线程状态流转
2.4.1 IDEA验证Java线程状态流转
2.4.2 控制台如何查看Java线程状态
我们可以简单理解为进程就是执行中的程序。进程是程序运行资源分配的最小单位。
操作系统中进程有五种状态,分别是:
上图就是经典的进程五状态模型。但事实上还存在被挂起的进程。
要说挂起、阻塞、睡眠难免让人想到进程生命周期中的阻塞态(等待状态),而挂起和睡眠却没有出现在上面的五状态模型图中,说明这三个其实在本质上区别并不那么大,但是既然称呼不同,应该就有不同的道理。事实上还存在被挂起的进程,所以在后面又拓展了进程状态模型,增加到了六状态和七状态模型图。
上面进程调度流程图中的三个基本状态(就绪态、运行态和阻塞态)为建立进程行为模型提供了一种系统方法,并指导操作系统的实现。
但是,可以证明往模型中增加其他状态也是合理的。下面考虑一个没有使用虚拟内存的系统,每次执行中的进程必须完全载入内存。因此,所有队列中的所有进程必须驻留在内存中。
对于I/O密集型的进程,一个进程进入等待(阻塞)状态后,处理器会转向处理另一个就绪的进程,但是由于处理器处理的速度相比I/O要快的多,以至于内存中所有进程都在等待I/O的情况很常见,所以可能会出现所有进程都处于阻塞状态的情况。导致处理器的效率低下。即使是多道程序设计,大多数时候处理器仍然可能处于空闲状态。
这种情况有两种解决办法:
交换(swapping)是一个I/O操作,因而可能使问题更恶化。但是由于磁盘I/O一般是系统中最快的I/O(相对于磁带或者打印机I/O),所以交换通常会提高性能
基于上面描述的一种现实问题,利用交换这个I/O操作,就产生了挂起这样一个新的状态。
现在有两种进程模型,一种是包含单挂起态的模型,一种是包含两个挂起态的模型。
在七状态模型中,新增了和挂起相关的两个概念,即阻塞挂起状态、就绪挂起状态:
与之前五个转换模型相比,比较重要的新转换如下:
还需要考虑的几种其他转换有:
通俗的说,挂起或者不挂起,不光要考虑为进程让出空间,不光要考虑是否就绪,还要考虑进程的优先级。
挂起的其他用途:到目前为止,挂起进程的概念与不在内存中的进程概念是等价的。一个不再内存中的进程,不论是否在等待一个事件,都不能立即执行,都需要再转换几个状态后才可以执行。
总结一下挂起进程的概念:
下面一张表展示挂起进程的原因:
在上一篇笔记中,讲过睡眠操作,但是我们发现睡眠状态并没有在七状态模型中出现,其实它是阻塞状态(等待状态)下的一种更细的分支。这样说的依据是进程由运行状态进入阻塞状态的原因,睡眠是进程通过代理(自己或父进程)主动引起的进程调度,并且这种阻塞状态恢复到就绪状态的时间是确定的。而狭义上的阻塞可以理解为一个被动的动作。
关于睡眠,有一篇博客是这样解释的:
线程是CPU调度和分派的基本单位,线程早期也有轻量级进程之称。一个进程中可能包含多个线程。一个进程至少包含一个线程,否则没有存在的意义。多线程里不宜再创建子进程。在系统内核层面,进程与线程并无本质的不同。进程与线程最大的不同点是资源分配。
同进程的实现原理类似,线程也可主要概括为五种状态(实际上Linux将线程状态细分为十几种):
基本和进程是一样的,线程调度的过程也可以直接参考进程的调度过程,两者都是一样的。
和进程的状态模型图基本是一样的。
多线程编程具有响应度高、资源共享、经济和多处理器体系结构的利用四个优点。用户线程是映射到内核线程池进行CPU调度的,映射关系模型包含有:
内核调度图:
void * func(void *p){printf("我是子线程\n");}int main(int argc, char *argv[]){pthread_attr_t attr; //定义一个变量pthread_t tid;pthread_attr_init(&attr);//初始化pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);//设置分离pthread_create(&tid, &attr, func, NULL);//创建线程sleep(1);//等1秒让子线程执行完pthread_attr_destroy(&attr);//释放return 0;
}
这一章节我们主要讲的内容有:
先来谈一谈Java 中线程的状态。在 java.lang.Thread.State 类是 Thread的内部枚举类,在里面定义了Java 线程的六个状态,注释信息也非常的详细。
public enum State {/*** Thread state for a thread which has not yet started.* 初始态,代表线程刚创建出来,但是还没有执行start()的状态*/NEW,/*** Thread state for a runnable thread. A thread in the runnable* state is executing in the Java virtual machine but it may* be waiting for other resources from the operating system* such as processor.* * 运行态,代表线程正在运行或者等待操作系统资源,如CPU资源*/RUNNABLE,/*** Thread state for a thread blocked waiting for a monitor lock.* A thread in the blocked state is waiting for a monitor lock* to enter a synchronized block/method or* reenter a synchronized block/method after calling* {@link Object#wait() Object.wait}.* * 阻塞态,代表线程正在等待一个监视器锁(即我们常说的synchronized)* 或者是在调用了Object.wait之后被notify()重新进入synchronized代码块*/BLOCKED,/*** Thread state for a waiting thread.* A thread is in the waiting state due to calling one of the* following methods:* * - {@link Object#wait() Object.wait} with no timeout
* - {@link #join() Thread.join} with no timeout
* - {@link LockSupport#park() LockSupport.park}
*
** A thread in the waiting state is waiting for another thread to* perform a particular action.** For example, a thread that has called Object.wait()* on an object is waiting for another thread to call* Object.notify() or Object.notifyAll() on* that object. A thread that has called Thread.join()* is waiting for a specified thread to terminate.* * 等待态,调用以下方法会进入等待状态:* 1. 调用不会超时的Object.wait()方法* 2. 调用不会超时的Thread.join()方法* 3. 调用不会超时的LockSupport.park()方法*/WAITING,/*** Thread state for a waiting thread with a specified waiting time.* A thread is in the timed waiting state due to calling one of* the following methods with a specified positive waiting time:*
* - {@link #sleep Thread.sleep}
* - {@link Object#wait(long) Object.wait} with timeout
* - {@link #join(long) Thread.join} with timeout
* - {@link LockSupport#parkNanos LockSupport.parkNanos}
* - {@link LockSupport#parkUntil LockSupport.parkUntil}
*
* * 超时等待态,在调用了以下方法后会进入超时等待状态* 1. Thread.sleep()方法后* 2. Object.wait(timeout)方法* 3. Thread.join(timeout)方法* 4. LockSupport.parkNanos(nanos)方法* 5. LockSupport.parkUntil(deadline)方法*/TIMED_WAITING,/*** Thread state for a terminated thread.* The thread has completed execution.* * 终止态,代表线程已经执行完毕*/TERMINATED;
}
关于上面JDK源码中对于BLOCKED状态的注释,这里有一点需要补充的,就是如果是线程调用了Object.wait(timeout)方法进入TIMED_WAITING状态之后,如果是因为超过指定时间,脱离TIMED_WAITING状态,如果接下去线程是要重新进入synchronize 代码块的话,也是会先进入等待队列,变成BLOCKED状态,然后请求监视器锁资源。
以上Java线程的六种状态分为正常状态3种:
异常状态3种:
超时等待(TIMED_WAITING)和等待(WAITING)的区别?
等待是在阻塞队列中,超时等待是在阻塞队列外。
很多人会有疑问,WAITING状态和BLOCKED状态有什么不同?
其实两者没有什么本质的不同,都是阻塞状态。
只不过WAITING是通过执行一些方法来触发的阻塞。而BLOCKED是等待锁引发的阻塞,他们两个的区别只是被阻塞的原因或者说背景不同,但是本质都是阻塞。BLOCKED、WAITING、TIMED_WAITIN这些状态其实都算是阻塞。
前面的章节已经讲过了,操作系统层面线程存在五类状态,状态的流转关系可以参考下面的这张图。
可以看到,Java 中所说的线程状态和操作系统层面的线程状态是不太一样的。
在Thread.State源码中也写了这么一句话:
These states are virtual machine states which do not reflect any operating system thread states.这些状态只是线程在虚拟机中的状态,并不反映操作系统的线程状态。
对于这两个层面对比,你需要知道的是,Java的线程状态是服务于监控的。从这个角度来考虑的话,把底层OS中的RUNNING和READY状态映射上来也没多大意义,因此,统一成为RUNNABLE 状态是不错的选择,而对WAITING状态更细致的划分,也是出于这么一个考虑。
Java本身是没有线程的,线程都是操作系统提供的。Java只是对操作系统提供的线程进行一些封装,让用户能更方便的使用操作线程。
Java线程只是操作系统线程在JVM中的一个映射而已,Java线程的状态并不代表操作系统中真实线程的状态。
关于阻塞状态,这里还要多说几句话,我们上面说的,都是在JVM代码层面的实际线程状态。但是在一些书比如《码出高效》中,会把Java 线程的阻塞状态分为:
线程不同状态之间的转化是谁来实现的呢?是JVM吗?
并不是。JVM需要通过操作系统内核中的TCB(Thread Control Block)模块来改变线程的状态,这一过程需要耗费一定的CPU资源。
这里演示一下,如何在IDEA 上面来验证上述的状态流转。有疑问或者有兴趣的读者可以按照同样的方法来验证。
我这里想要用代码验证下面的情况:
public class ThreadLifeTempTest {public static void main(String[] args) {Object object = new Object();new Thread(()->{synchronized (object) {try {System.out.println("thread1 waiting");// 调用wait会释放锁(这就给了Thread2机会来获取到object对象锁),等待10s,进入Timed_Waiting// 10s 后会继续执行后续synchronized代码块中的代码,但是因为获取不到锁,就会进入Blocked,获取object的监视器锁object.wait(10000);System.out.println("thread1 after waiting");} catch (InterruptedException e) {e.printStackTrace();}}}, "Thread1").start();new Thread(()->{synchronized (object) {try {// sleep也不会释放锁,所以Thread1 不会获取到锁Thread.sleep(10000000);} catch (InterruptedException e) {e.printStackTrace();}}}, "Thread2").start();}
}
使用IDEA的RUN模式运行代码,然后点击左边的一个摄像头按钮(dump thread),查看各线程的状态。
在Thread 1 等待 10s中时,dump的结果:Thread 1和Thread 2都处于TIMED_WAITING状态:
"Thread2" #13 prio=5 os_prio=0 tid=0x0000000020196800 nid=0x65b8 waiting on condition [0x0000000020afe000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at main.java.concurrent.thread.ThreadLifeTempTest.lambda$main$1(ThreadLifeTempTest.java:33)- locked <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest?Lambda$2/1096979270.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
"Thread1" #12 prio=5 os_prio=0 tid=0x0000000020190800 nid=0x25fc in Object.wait() [0x00000000209ff000]java.lang.Thread.State: TIMED_WAITING (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest.lambda$main$0(ThreadLifeTempTest.java:21)- locked <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest?Lambda$1/1324119927.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
在Thread 1 等待 10s之后,Thread 1重新进入synchronize 代码块,进入等待队列,变成BLOCKED状态:
"Thread2" #13 prio=5 os_prio=0 tid=0x0000000020196800 nid=0x65b8 waiting on condition [0x0000000020afe000]java.lang.Thread.State: TIMED_WAITING (sleeping)at java.lang.Thread.sleep(Native Method)at main.java.concurrent.thread.ThreadLifeTempTest.lambda$main$1(ThreadLifeTempTest.java:33)- locked <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest?Lambda$2/1096979270.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
"Thread1" #12 prio=5 os_prio=0 tid=0x0000000020190800 nid=0x25fc waiting for monitor entry [0x00000000209ff000]java.lang.Thread.State: BLOCKED (on object monitor)at java.lang.Object.wait(Native Method)- waiting on <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest.lambda$main$0(ThreadLifeTempTest.java:21)- locked <0x000000076b71c748> (a java.lang.Object)at main.java.concurrent.thread.ThreadLifeTempTest?Lambda$1/1324119927.run(Unknown Source)at java.lang.Thread.run(Thread.java:748)
步骤:
如何排查死锁的流程就是这个样子:使用jps获取PID,然后再用jstack查看这个PID中的线程的运行状态。
相关文章:【并发基础】一篇文章带你彻底搞懂睡眠、阻塞、挂起、终止之间的区别
【并发基础】Java中线程的创建和运行以及相关源码分析
【并发基础】线程,进程,协程的详细解释