线程从创建到销毁,经历了数个阶段,一般情况下我们是用不到具体了解其状态,但是一旦在分析某些阻塞、CPU高等问题时,还是需要通过分析具体线程的栈信息、CPU占用等信息来判断问题点。所以全面的了解线程的状态信息还是很有必要的。
我们先来看一下线程状态变化图(来自<<Java并发编程艺术>>):
具体更多线程状态知识点可以参考: 线程的几种状态转换 - 积极解放 - 博客园
我们下面通过代码实战方式来了解下各种状态变化。
Arthas(笔者使用阿里开源的Arthas来分析),后续展示都是通过该工具来操作的
进程占用CPU过高的问题也是一个比较常见的问题,网络上也有很多分析在Linux环境下如何查找进程中哪个线程耗用CPU的,所以笔者不再花费大篇幅来介绍这个问题,简单实战下。
public void testRunning() { // 创建线程,指定线程名,任务为无限循环,达到高占用CPU的目的 Thread thread = new Thread(new Runnable() { int i = 0; @Override public void run() { while (true) { i++; } } }, "thread-1-running-test"); thread.start(); try { // 阻塞当前test方法,使主线程阻塞 new CountDownLatch(1).await(); } catch (InterruptedException e) { e.printStackTrace(); } }
启动该test方法后,使用Arthas来连接对应进程。
2.2.1 执行dashboard命令
可以看到我们创建的这个线程thread-1-running-test占用CPU最高。
有关于线程栈的具体信息我们可以通过以下命令来查看
2.2.2 thread 查看线程栈信息
[arthas@16324]$ thread 13 thread 13 "thread-1-running-test" Id=13 RUNNABLE at ThreadTest$1.run(ThreadTest.java:21) at java.lang.Thread.run(Thread.java:748)
当前线程id=13,名称就是我们自定义命名的thread-1-running-test,状态为RUNNABLE
这样我们通过对线程栈信息的分析就可以知道具体是哪里的代码造成CPU飙高了。
有关于RUNNABLE状态的分析:
RUNNABLE对应为可运行状态,对应操作系统中线程的两种状态:RUNNING、READY,处于RUNNABLE状态的线程有可能正在运行,也有可能在等待CPU分配时间分配
public void testSyncBlock() { final Object obj = new Object(); // 创建线程,指定线程名, Thread thread = new Thread(new Runnable() { int i = 0; @Override public void run() { synchronized (obj) { while(true) { i++; } } } }, "thread-1-block-test"); thread.start(); // 创建线程,指定线程名 Thread thread2 = new Thread(new Runnable() { int i = 0; @Override public void run() { synchronized (obj) { while(true) { i++; } } } }, "thread-2-block-test"); thread2.start(); try { // 阻塞当前test方法,使主线程阻塞 new CountDownLatch(1).await(); } catch (InterruptedException e) { e.printStackTrace(); } }
同样的,我们先通过dashboard命令查看两个线程ID,笔者查询出分别为13 14,下面通过thread命令查看具体栈信息
// 查看线程13的栈信息 [arthas@20524]$ thread 13 thread 13 "thread-1-block-test" Id=13 RUNNABLE at ThreadTest$2.run(ThreadTest.java:47) at java.lang.Thread.run(Thread.java:748) // 查看线程14的栈信息 [arthas@20524]$ thread 14 thread 14 "thread-2-block-test" Id=14 BLOCKED on java.lang.Object@247b3634 owned by "thread-1-block-test" Id=13 at ThreadTest$3.run(ThreadTest.java:62) - blocked on java.lang.Object@247b3634 at java.lang.Thread.run(Thread.java:748)
有关于线程 thread-2-block-test 目前状态是BLOCKED,可以看到其被阻塞到Object@247b3634上了(也就是我们自定义的Object)等待其他对象释放锁。
目前锁被thread-1-block-test线程锁持有
public void testWaitBlock() { final Object obj = new Object(); // 创建线程,指定线程名 Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "thread-1-block-test"); thread.start(); // 创建线程,指定线程名 Thread thread2 = new Thread(new Runnable() { @Override public void run() { synchronized (obj) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "thread-2-block-test"); thread2.start(); try { // 阻塞当前test方法,使主线程阻塞 new CountDownLatch(1).await(); } catch (InterruptedException e) { e.printStackTrace(); } }
同样的,我们先通过dashboard命令查看两个线程ID,笔者查询出分别为13 14,下面通过thread命令查看具体栈信息
[arthas@1312]$ thread 13 thread 13 "thread-1-block-test" Id=13 WAITING on java.lang.Object@6ce36671 at java.lang.Object.wait(Native Method) - waiting on java.lang.Object@6ce36671 at java.lang.Object.wait(Object.java:502) at ThreadTest$4.run(ThreadTest.java:88) at java.lang.Thread.run(Thread.java:748) [arthas@1312]$ thread 14 thread 14 "thread-2-block-test" Id=14 WAITING on java.lang.Object@6ce36671 at java.lang.Object.wait(Native Method) - waiting on java.lang.Object@6ce36671 at java.lang.Object.wait(Object.java:502) at ThreadTest$5.run(ThreadTest.java:103) at java.lang.Thread.run(Thread.java:748)
可以看到两个线程的状态均为WAITING,在等待对象java.lang.Object@6ce36671的唤醒
还有一种调用方式会将线程状态置为WAITING,那就是ReentrantLock
public void testLock() { final ReentrantLock lock = new ReentrantLock(); // 创建线程,指定线程名 Thread thread = new Thread(new Runnable() { int i = 0; @Override public void run() { lock.lock(); while(true) { i++; } } }, "thread-1-block-test"); thread.start(); // 创建线程,指定线程名 Thread thread2 = new Thread(new Runnable() { @Override public void run() { lock.lock(); } }, "thread-2-block-test"); thread2.start(); try { // 阻塞当前test方法,使主线程阻塞 new CountDownLatch(1).await(); } catch (InterruptedException e) { e.printStackTrace(); } }
通过thread命令可以看到两个线程的状态不同
[arthas@6920]$ thread 13 thread 13 "thread-1-block-test" Id=13 RUNNABLE at ThreadTest$8.run(ThreadTest.java:175) at java.lang.Thread.run(Thread.java:748) [arthas@6920]$ thread 14 thread 14 "thread-2-block-test" Id=14 WAITING on java.util.concurrent.locks.ReentrantLock$NonfairSync@b29a61e owned by "thread-1-block-test" Id=13 at sun.misc.Unsafe.park(Native Method) - waiting on java.util.concurrent.locks.ReentrantLock$NonfairSync@b29a61e at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
thread-1-block-test线程后去到锁,后续在执行任务;
thread-2-block-test没有获取到锁,所以一直在等待thread-1-block-test线程释放锁。
public void testTimeWaitBlock() { final Object obj = new Object(); // 创建线程,指定线程名 Thread thread = new Thread(new Runnable() { @Override public void run() { synchronized (obj) { try { obj.wait(100000); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "thread-1-block-test"); thread.start(); // 创建线程,指定线程名 Thread thread2 = new Thread(new Runnable() { @Override public void run() { synchronized (obj) { try { obj.wait(100000); } catch (InterruptedException e) { e.printStackTrace(); } } } }, "thread-2-block-test"); thread2.start(); try { // 阻塞当前test方法,使主线程阻塞 new CountDownLatch(1).await(); } catch (InterruptedException e) { e.printStackTrace(); } }
同样的,我们先通过dashboard命令查看两个线程ID,笔者查询出分别为13 14,下面通过thread命令查看具体栈信息
[arthas@19356]$ thread 13 thread 13 "thread-1-block-test" Id=13 TIMED_WAITING on java.lang.Object@18e58d49 at java.lang.Object.wait(Native Method) - waiting on java.lang.Object@18e58d49 at ThreadTest$6.run(ThreadTest.java:131) at java.lang.Thread.run(Thread.java:748) [arthas@19356]$ thread 14 thread 14 "thread-2-block-test" Id=14 TIMED_WAITING on java.lang.Object@18e58d49 at java.lang.Object.wait(Native Method) - waiting on java.lang.Object@18e58d49 at ThreadTest$7.run(ThreadTest.java:146) at java.lang.Thread.run(Thread.java:748)
同样,关于TIMED_WAITING状态,通过ReentrantLock.lock(time)也会获取到,笔者不再演示。
通过对示例的分析,我们总结了关于线程的几种状态,明确学习之后,我们再来分析问题时就能做到心中有数,看到对应的状态就能知道线程被执行了哪些方法,进而通过线程栈确定问题点。