前言
在我们日常开发过程中避免不了跟并发编程打交道,而并发编程的核心自然就是线程。搞清楚线程的生命周期以及状态的转换对于我们排查某些线上问题有着至关重要的作用。
操作系统通用线程,java线程傻傻分不清楚
1.操作系统通用线程
操作系统通用线程的生命周期主要分为五种状态:初始状态,可运行状态,运行状态,休眠状态,终止状态。
初始态:指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。
可运行态: 指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。
运行态:当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到 CPU 的线程的状态就转换成了运行状态。
休眠态:运行状态的线程如果调用一个阻塞的 API或者一直等待某个条件可用时,线程的状态就会转换到休眠状态,同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的条件满足了,线程就会从休眠状态转换到可运行状态。
终止态:线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。
2.java线程
java线程的生命周期状态有哪些呢?打开Thread类我们会发现State枚举,这个枚举中的所有状态就是java线程的生命周期状态
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线程生命周期状态主要有NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED六种状态
操作系统通用线程和java线程的映射关系
java线程状态转换验证
1.NEW
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread();
System.out.println(thread.getState());
//NEW
}
}
2.RUNNABLE
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
while (true){
}
}
};
thread.start();
System.out.println(thread.getState());
//RUNNABLE
}
}
3.RUNNABLE -> BLOCK
注意:有且只有synchronized同步会使线程转换为BLOCK状态。而J.U.C包下的Lock锁会使线程状态转为WATING,因为Lock锁的实现基于AQS,而AQS是通过 LockSupport.park()方式阻塞当前线程的。以后有空我会写一篇关于AQS的源码解析文章,这里就不做深入探讨了。
/**
使用synchronized锁进行线程同步
*/
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread thread1 = new Thread(){
@Override
public void run() {
synchronized (o){
System.out.println(Thread.currentThread()+":获取锁");
while (true){
}
}
}
};
thread1.setName("thread1");
thread1.start();
Thread.sleep(1000);//等待1S再启动thread2线程,保证thread2比thread1晚启动
Thread thread2 = new Thread(){
@Override
public void run() {
synchronized (o){
System.out.println(Thread.currentThread()+":获取锁");
}
}
};
thread2.setName("thread2");
thread2.start();
System.out.println("thread2刚启动时状态为:"+thread2.getState());
Thread.sleep(1000);
System.out.println("thread2一秒后状态为:"+thread2.getState());
}
}
/**运行结果如下:
Thread[thread1,5,main]:获取锁
thread2刚启动时状态为:RUNNABLE
thread2一秒后状态为:BLOCKED
*/
下面验证下Lock锁会使阻塞线程状态转为什么
/**
使用lock锁进行线程同步
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Thread thread1 = new Thread(){
@Override
public void run() {
try{
lock.lock();
System.out.println(Thread.currentThread()+"获取锁");
while (true){
}
}finally {
lock.unlock();
}
}
};
thread1.setName("thread1");
thread1.start();
Thread.sleep(1000);//等待1S再启动thread2线程,保证thread2比thread1晚启动
Thread thread2 = new Thread(){
@Override
public void run() {
try{
lock.lock();
System.out.println(Thread.currentThread()+"获取锁");
}finally {
lock.unlock();
}
}
};
thread2.setName("thread2");
thread2.start();
System.out.println("thread2刚启动时状态为:"+thread2.getState());
Thread.sleep(1000);
System.out.println("thread2一秒后状态为:"+thread2.getState());
}
}
/**
运行结果:
Thread[thread1,5,main]获取锁
thread2刚启动时状态为:RUNNABLE
thread2一秒后状态为:WAITING
*/
通过上面的Demo演示可以看到Lock锁是把线程状态转换为WATING
4.RUNNABLE -> WAITING
1)调用wait()
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread thread = new Thread(){
@Override
public void run() {
synchronized (object){
try {
System.out.println(Thread.currentThread()+":获取锁");
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.setName("Thread");
thread.start();
System.out.println("Thread start()后状态:"+thread.getState());
Thread.sleep(3000);
System.out.println("Thread 调用wait()后状态:"+thread.getState());
/**
* Thread start()后状态:RUNNABLE
* Thread[Thread,5,main]:获取锁
* Thread 调用wait()后状态:WAITING
*/
}
}
2)调用join()
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(){
@Override
public void run() {
while (true){
//空转保证该线程一直运行
}
}
};
thread1.setName("Thread1");
thread1.start();
Thread thread2 = new Thread(){
@Override
public void run() {
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread2.setName("Thread2");
thread2.start();
System.out.println("thread2 start()之后状态"+thread2.getState());
Thread.sleep(3000);
System.out.println("thread2 join()之后状态"+thread2.getState());
/**
* thread2 start()之后状态RUNNABLE
* thread2 join()之后状态WAITING
*/
}
}
3)调用LockSupport.park()
注意:在java6之后在park系列方法新增加了入参Object blocker,用于标识阻塞对象,该对象主要用于问题排查和系统监控。
import java.util.concurrent.locks.LockSupport;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
LockSupport.park();
}
};
thread.start();
System.out.println("Thread 调用start() 状态为:"+thread.getState());
Thread.sleep(3000);
System.out.println("Thread 被LockSupport.park()阻塞后 状态为:"+thread.getState());
/**
* Thread 调用start() 状态为:RUNNABLE
* Thread 被LockSupport.park()阻塞后 状态为:WAITING
*/
}
}
讲到这里,小伙伴可能有疑问,J.U.C包下的Condition.await()也可以使线程进入等待,为什么不讲呢?那我们来打开源码一探究竟。哦嚯,原来底层调用的就是LockSupport.park()
5.RUNNABLE -> TIMED_WAITING
1)调用wait(long time)
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
Thread thread = new Thread(){
@Override
public void run() {
synchronized (object){
try {
object.wait(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
System.out.println("Thread 调用start() 状态为:"+thread.getState());
Thread.sleep(3000);
System.out.println("Thread 被wait(long time)阻塞后 状态为:"+thread.getState());
/**
*Thread 调用start() 状态为:RUNNABLE
*Thread 被wait(long time)阻塞后 状态为:TIMED_WAITING
*/
}
}
2)调用Thread.sleep(long time)
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread.start();
System.out.println("Thread 调用start() 状态为:"+thread.getState());
Thread.sleep(3000);
System.out.println("Thread 被Thread.sleep(long time)休眠后 状态为:"+thread.getState());
/**
*Thread 调用start() 状态为:RUNNABLE
*Thread 被Thread.sleep(long time)休眠后 状态为:TIMED_WAITING
*/
}
}
3)调用join(long time)
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(){
@Override
public void run() {
while (true){
//空转保证该线程一直运行
}
}
};
thread1.setName("Thread1");
thread1.start();
Thread thread2 = new Thread(){
@Override
public void run() {
try {
thread1.join(10000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
thread2.setName("Thread2");
thread2.start();
System.out.println("thread2 start()之后状态"+thread2.getState());
Thread.sleep(3000);
System.out.println("thread2 join(long time)之后状态"+thread2.getState());
/**
* thread2 start()之后状态RUNNABLE
* thread2 join(long time)之后状态TIMED_WAITING
*/
}
}
4)调用parkUntil(long deadline)
注意:在java6之后在park系列方法新增加了入参Object blocker,用于标识阻塞对象,该对象主要用于问题排查和系统监控。
import java.util.concurrent.locks.LockSupport;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(){
@Override
public void run() {
long beginTime = System.currentTimeMillis();
LockSupport.parkUntil(beginTime + 10000);
long endTime = System.currentTimeMillis();
System.out.println("阻塞秒数:"+(endTime - beginTime)/1000);
}
};
thread1.setName("Thread1");
thread1.start();
System.out.println("Thread1 调用start() 状态为:"+thread1.getState());
Thread.sleep(3000);
System.out.println("Thread1 调用parkUntil(long deadline) 休眠后 状态为:"+thread1.getState());
/**
* Thread 调用start() 状态为:RUNNABLE
* Thread 被Thread.sleep(long time)休眠后 状态为:TIMED_WAITING
* 阻塞秒数:10
*/
}
}
5)调用parkNanos(long nanos)
注意:在java6之后在park系列方法新增加了入参Object blocker,用于标识阻塞对象,该对象主要用于问题排查和系统监控。
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(){
@Override
public void run() {
long beginTime = System.currentTimeMillis();
LockSupport.parkNanos(1000000L*10000);//10S
long endTime = System.currentTimeMillis();
System.out.println("阻塞秒数:"+(endTime - beginTime)/1000);
}
};
thread1.setName("Thread1");
thread1.start();
System.out.println("Thread1 调用start() 状态为:"+thread1.getState());
Thread.sleep(3000);
System.out.println("Thread1 调用parkNanos(long nanos) 休眠后 状态为:"+thread1.getState());
/**
*Thread1 调用start() 状态为:RUNNABLE
*Thread1 调用parkNanos(long nanos) 休眠后 状态为:TIMED_WAITING
*阻塞秒数:10
*/
}
}
注意:Lock锁对应的condition.await(long time, TimeUnit unit)底层调用的还是LockSupport相关的API,这里不对await(long time, TimeUnit unit)进行演示
6.BLOCK -> RUNNABLE
BLOCK线程拿到锁资源就会转换状态为RUNNABLE(代码演示略)
7.WATING -> RUNNABLE
调用等待API所对应的唤醒API即可(代码演示略)
8.TIMED_WATING -> RUNNABLE
调用等待API所对应的唤醒API或者等待时间到期即可(代码演示略)
9.RUNNING -> TERMINATED
线程执行完毕或者被中断的时候状态就会转为TERMINATED(代码演示略)
线上系统如何查看线程状态
前面我们演示了通过getState()来拿到线程的状态,但是线上环境不可能让你通过上述代码的方式去检查服务中每个线程当前的运行情况。那么,这里就不得不提我们的装X利器jstack
上面是jstack命令所有的参数,这里我们直接使用jstack <pid> > <File> 把进程ID为pid的服务内所有的线程状态输出到指定文件中。小伙伴们,合理利用jdk自带的分析工具可以让我们处理一些线上问题有着事半功倍的效果,后续有时间我会详细的讲一下相关工具的使用,这里就不多做阐述了。
测试案例:前面我们说过condition.await底层调用的是LockSupport.park(Object blocker),这里就通过jstack生成jstack文件来验证一下。
测试代码:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Thread thread = new Thread(){
@Override
public void run() {
try{
lock.lock();
System.out.println(Thread.currentThread()+"获取锁,开始等待");
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
};
thread.setName("Thread-Test");
thread.start();
}
}
1.运行测试代码
通过测试代码我们可以看到我们自定义的线程名称是【Thread-Test】,这里提醒一下小伙伴,使用线程或者线程池的时候,一定要给它们起一个名字,方便后期问题排查和定位。
2.通过jdk自带的jps命令查找进程
命令:jps
这里我们看到测试案例对应的进程ID是11672
3.通过jstack生成当前进程的stack日志
命令: jstack 11672 > thread.log
4.分析生成的stack日志
通过线程名我们就可以轻松找到自定义的线程,从信息中可以看到调用了LockSupport.park方法,当前线程状态是WAITING,跟我们前面得出的结论一致。
结束语
这篇文章主要讲述了线程的生命周期,了解线程的生命周期才能更透彻的分析系统的运行情况,方便定位某些复杂的线上问题。这里我们提到了jstack命令,这个命令很强大,可以帮我们迅速定位死锁,线上CPU暴涨等情况。后续有时间会写一篇关于jstack的文章,谢谢大家。