写这个笔记的起因是最近在分析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线程不同状态之间的转换
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线程状态流转图,方便记忆
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多,说明我这个系统是有问题的)
线程中断
除了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对象,该对象不断从队列内取出任务进行执行,如果队列内任务为空,那么就会在该队列阻塞
getTask方法代码截图如下:
其中代码@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验证截图如下
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时候忽略这些线程
2.比如下面这个,虽然处于RUNNABLE状态,实际上是阻塞在监听接入连接上(阻塞在网络IO上),因此实际是不使用cpu资源
3.如下图这个,线程状态是RUNNABLE,这里是使用正则表达式解析,正在使用cpu资源
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线程模型各状态转换关系如下图