首页 > 技术文章 > java线程生命周期

zhangyjblogs 2020-12-20 15:00 原文

写这个笔记的起因是最近在分析cpu问题,通过jstack查看线程堆栈时候,发现线程的多个状态,哪些状态表示使用cpu,对此有些疑惑,进而记录下。

java线程生命周期

java线程的定义

java线程状态可通过Thread.getStatus()方法的枚举值java.lang.Thread.State查看,先贴下源码

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        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.
         */
        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}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        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:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

根据源码内的定义,java线程状态有6种,分别是

线程状态 定义 备注
NEW 线程创建后还未运行状态,即就绪状态。 Thread.start()刚执行后的线程状态,此时线程还未开始执行
RUNNABLE 在Java虚拟机中执行的线程处于这种状态。说明线程正在处于运行状态 可运行线程的线程状态,说明线程正在执行或者在等待资源(比如磁盘io、网络io)
BLOCKED 线程处于阻塞状态,在等待监视器锁 说明处于这个状态的线程是执行到了synchronized代码块和synchronized方法
WAITING 线程处于等待状态 线程执行了以下操作会处于此状态:Object#wait(),Thread.join,LockSupport#park()
TIMED_WAITING 线程处于等待状态,但是有指定的等待的时间 线程执行了以下操作会处于此状态:Thread.sleep(long millis),Object.wait(long timeout),Thread.join(long millis),LockSupport.parkNanos(Object blocker, long deadline),LockSupport.parkUntil(long deadline)。
TERMINATED 线程执行结束后处于此状态 线程执行完 run() 方法后或者执行run()抛出了异常,会自动转换到 此状态

由上可知TIMED_WAITING 和 WAITING 状态的区别,仅仅是触发条件多了超时参数。

从上面定义可知,BLOCKED、WAITING、TIMED_WAITING都是说明线程处于阻塞状态,即阻塞状态分了这三个情况。

java线程不同状态之间的转换

image-20201129205635146

NEW -> RUNNABLE 状态

Java 刚创建出来的 Thread 对象就是 NEW 状态, NEW 状态的线程,不会被操作系统调度,因此不会执行。Java 线程要执行,就必须转换到 RUNNABLE 状态。从 NEW 状态转换到 RUNNABLE 状态只要调用线程对象的 start() 方法就可以。

RUNNABLE -> BLOCKED状态

代码执行到synchronized代码块和synchronized方法 时候要获取到锁,就变成BLOCKED状态。

但是有个问题,比如调用阻塞IO操作,比如磁盘io java.io.FileInputStream.read(byte[]),网络阻塞io java.net.SocketInputStream.read(byte[])时候线程状态还是RUNNABLE ,并非会转移到BLOCKED状态。虽然在操作系统层面,此时的动作是阻塞的(操作系统线程会转移到阻塞状态),但是JVM层面并不关心操作系统调度相关的状态,因为在 JVM 看来,等待 CPU 使用权(操作系统层面此时处于可执行状态)与等待 I/O(操作系统层面此时处于休眠状态)没有区别,都是在等待某个资源,所以都归入了 RUNNABLE 状态。我们平时所谓的 Java 在调用阻塞式 API 时,线程会阻塞,指的是操作系统线程的状态,并不是 Java 线程的状态。

RUNNABLE -> WAITING状态

根据定义,线程执行到代码Object#wait(),Thread.join,LockSupport#park()时候线程会转移到该前状态。

RUNNABLE -> TIMED_WAITING状态

根据定义线程执行到Thread.sleep(long millis),Object.wait(long timeout),Thread.join(long millis),LockSupport.parkNanos(Object blocker, long deadline),LockSupport.parkUntil(long deadline),和waitting相比主要是有个等待时间,且比waiting多了个sleep操作。

RUNNABLE -> TERMINATED状态

线程执行完 run() 方法后或者执行run()抛出了异常,会自动转换到 此状态。该状态通常不需要关注。

BLOCKED -> RUNNABLE状态

线程获得 监视器锁(synchronized 隐式锁)时,就又会从 BLOCKED 转换到 RUNNABLE 状态

WAITING -> RUNNABLE状态

对于Object.wait(),其它线程执行了同一个对象的Object#notify()或者nofityAll()时候唤醒线程,线程由WAITING -> RUNNABLE。

对于Thread.join(),其它线程执行完毕或者抛出异常后唤醒线程,线程由WAITING -> RUNNABLE。

对于LockSupport#park,在别处执行了此Thread的LockSuport#unPark时候唤醒线程,线程由WAITING -> RUNNABLE。

TIMED_WAITING -> RUNNABLE状态

对于Object.wait(long timeout),线程阻塞timeout时间后,或者在timeout时间内其它线程执行了同一个对象的Object#notify()或者nofityAll()时候唤醒线程,线程由WAITING -> RUNNABLE。

对于Thread.join(long millis),线程阻塞millis时间后,或者millis时间内其它线程执行完毕或者抛出异常后唤醒线程,线程由WAITING -> RUNNABLE。

对于LockSupport.parkNanos(Object blocker, long deadline)和LockSupport.parkUntil(long deadline),线程阻塞deadline时间后,或者在别处执行了此Thread的LockSuport#unPark时候唤醒线程,线程由WAITING -> RUNNABLE。

Thread.sleep(long millis),休眠millis时间后线程自动唤醒,或者线程被打断休眠,也会被唤醒。线程由WAITING -> RUNNABLE。

java线程状态流转图,方便记忆

image-20201129215148870

jstack显示的java线程状态

通过通过jstack PID生成线程堆栈文件,里面的线程状态以及解释如下如下

java.lang.Thread.State: BLOCKED 线程处于阻塞状态,遇到了synchronized处于阻塞状态

java.lang.Thread.State: RUNNABLE 线程处于正运行状态,或者阻塞在IO状态
java.lang.Thread.State: TIMED_WAITING (on object monitor) Object.wait(long)加超时时间操作
java.lang.Thread.State: TIMED_WAITING (parking) 通过LockSupport.parkXXX加超时时间操作
java.lang.Thread.State: TIMED_WAITING (sleeping) 通过Thread.sleep加休眠时间操作
java.lang.Thread.State: WAITING (on object monitor) Object.wait()无超时时间操作
java.lang.Thread.State: WAITING (parking) 通过LockSupport.park())操作

如果公司有监控,可以通过监控查看线程状态,如下图(我这个图内waiting有3w多,说明我这个系统是有问题的)

image-20201130213510780

线程中断

除了Object.nofity(),nofityAll(),LockSupport.unPark(Thread)操作唤醒线程或者超时自动唤醒线程,工作中也经常使用到interrupt() 方法对阻塞中的线程(WAITING、TIMED_WAITING 状态)进行中断,以便使线程从阻塞(WAITING、TIMED_WAITING 状态)中被唤醒。interrupt()操作是给正在处于阻塞状态的线程发个中断通知,以便线程从阻塞中唤醒过来。

线程 A 处于 WAITING、TIMED_WAITING 状态时,如果其他线程调用线程 A 的 interrupt() 方法,会使线程 A 返回到 RUNNABLE 状态(中间可能存在BLOCKED状态),同时线程 A 的代码会触发 InterruptedException 异常。通过查看Object.wait(),Thread.join()方法上都有throws InterruptedException。这个异常的触发条件就是:其他线程调用了该线程的 interrupt() 方法。

当RUNNABLE状态线程在阻塞到IO操作时候,此时线程状态还是RUNNABLE ,但是实际在linux线程模型中是阻塞状态,比如线程A阻塞在 java.nio.channels.Selector.select() 上时,如果其他线程调用线程 A 的 interrupt() 方法,线程 A 的 java.nio.channels.Selector 会立即返回。

线程池内线程的状态

平常使用线程都是使用的线程池ThreadPoolExecutor,那么线程池内的线程的通常是处于什么状态呢?

比如new ThreadPoolExecutor(10, 10, 60L,TimeUnit.MINUTES, new LinkedBlockingQueue(1024))

在预热阶段,线程状态也是从NEW->RUNNABLE-BLOCKED/WAITING/TIMED_WAITING状态,等待10个线程全部启动后,此时如果线程池没有请求接入,那么线程池内的线程状态是waiting,如何分析的呢?

线程池活动达到core线程后,接入的请求都会存放到队列内,预热的线程是java.util.concurrent.ThreadPoolExecutor.Worker对象,该对象不断从队列内取出任务进行执行,如果队列内任务为空,那么就会在该队列阻塞

image-20201129223634703

getTask方法代码截图如下:

image-20201129224212083

其中代码@1和@2最终都是使用的LockSupport.park()挂起线程,因此,线程池内的线程空闲时候,线程状态是waiting状态。

验证如下:

public class AllocateServiceImpl implements AllocateService, InitializingBean{

	private ThreadPoolExecutor pool;
    
    @Override
	public void afterPropertiesSet() throws Exception {
		ThreadFactory threadFactory = new ThreadFactoryBuilder().
	            setNameFormat("my-pool-%d").build();
		pool = new ThreadPoolExecutor(10, 10, 60L, TimeUnit.MINUTES, 
				new LinkedBlockingDeque<Runnable>(1024), threadFactory, new ThreadPoolExecutor.AbortPolicy());
		pool.prestartAllCoreThreads();//启动所有线程
	}
}

启动后使用jstack pid验证截图如下

image-20201129232213330

java线程状态对应使用cpu

java线程的不同状态,是否使用cpu资源呢?

NEW 这个好理解,线程刚创建,还未执行,并不使用cpu
RUNNABLE 线程处于可运行状态,但是实际可能是正在运行或者等待io资源,因此不能完全确定是否在使用cpu资源。
BLOCKED 线程阻塞状态,肯定不会使用cpu资源
WAITING 线程休眠状态,肯定不会使用cpu资源
TIMED_WAITING 线程休眠状态,肯定不会使用cpu资源

通过上面分析,在分析cpu100%问题时候,jstack PID 后,只需要查看dump结果中处于RUNNABLE状态的线程即可,但是处于RUNNABLE状态的线程,不代表就一定正在使用cpu资源,因此要特定分析(通过代码分析)。通常是通过top -Hp PID确定使用cpu资源高的线程后,再通过多次jstack操作,查看哪些线程堆栈一直处于RUNNABLE。

举例如下:

1.处于waiting,timed_waiting,blocked状态肯定不消化cpu,如下图,分析cpu时候忽略这些线程

image-20201129233245589

2.比如下面这个,虽然处于RUNNABLE状态,实际上是阻塞在监听接入连接上(阻塞在网络IO上),因此实际是不使用cpu资源

image-20201129233448852

3.如下图这个,线程状态是RUNNABLE,这里是使用正则表达式解析,正在使用cpu资源

image-20201129233731839

sleep、yield、wait、join的区别

首先sleep、wait、join都会使线程进入阻塞状态(waiting/timed_waiting状态),同时也都会释放cpu资源(因为状态非runnable状态都不消耗cpu资源)

yield是释放cpu资源,然后又抢夺cpu资源,目的是为了让其它线程有机会获取cpu资源进行处理,但是线程状态还是runnable。

sleep如果是在锁方法内执行,比如同步代码块或者重入锁方法内执行,是不会释放资源。而wait会释放锁资源。

wait用于锁机制,sleep不是,这也是为什么sleep不释放锁,wait释放锁的原因,sleep是线程的方法,跟锁没关系,wait,notify,notifyall 都是Object对象的方法,是一起使用的,用于锁机制。

有个特殊的Thread.sleep(0),操作这个动作是让出cpu,让其它线程又机会获取cpu资源执行,但是执行这个动作,线程也会处于time_waiting状态吗?然后立刻又恢复runnable状态,不知道自己这个理解是否正确,也无法验证。

通用线程模型和JVM线程模型关系

linux线程的4种状态

linux线程状态 状态 含义 java线程状态
就绪(Ready) 线程能够运行,但在等待可用的处理器。可能刚刚创建启动,或刚刚从阻塞中恢复,或者被其他线程抢占 Runnable (在JVM内,把可运行状态和运行状态归为了一个RUNNABLE,这样做的原因是JVM把线程的调度交给操作系统,并不关心这两个状态)
运行(Running) 线程正在运行。在多处理器系统中,可能有多个线程处于运行态
阻塞(Blocked) 线程由于等待处理器外的其他条件无法运行,如条件变量的改变、加锁互斥量或I/O操作结束 Blocked/Waiting/timed_waiting
终止(Terminated) 线程从起始函数中返回,或者调用pthread_exit,或者被取消,终止自己并完成所有资源清理工作。不是被分离,也不是被连接 Terminated

linux线程模型各状态转换关系如下图

image-20201130213731203

参考 https://www.cnblogs.com/xidongyu/p/10962657.html

推荐阅读