看很多同学的面经、网上的面试资料,都不约而同的提到了一个基础问题:“你知道线程有几种状态吗?状态之间的扭转是怎样的?”,有准备的同学都知道有五种:New(新建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、Timed waiting(计时等待)、Terminated(被终止), 通过new、wait、notify、sychroniezd、lock等操作进行状态扭转。
但,这是面试官的期待的答案吗? 面试官考察的仅仅是你的Java基础吗? 在实际工作中,线程状态有用吗?
下面通过一个线上排查case来回答这个问题。
有一个线上离线数据服务,每天早上定时发送日报给产品。有一天产品反馈说数据没了,到公司赶紧排查,查看日志、监控发现任务确实没有执行,日志里也没发现任何Error或者Exception,怀疑可能是代码某个位置卡住了,通过 jstack 查看线程堆栈,发现“GET-DATA”全部处于WAITING状态
"GET-DATA-14" #14 prio=5 os_prio=31 tid=0x00007fa317a94800 nid=0x5503 waiting on condition [0x0000700003f68000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b546838> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at jstack.JstackTest$1.call(JstackTest.java:30)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
"GET-DATA-13" #13 prio=5 os_prio=31 tid=0x00007fa31811d800 nid=0x3e03 waiting on condition [0x0000700003e65000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000076b68e258> (a java.util.concurrent.FutureTask)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.FutureTask.awaitDone(FutureTask.java:429)
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at jstack.JstackTest$1.call(JstackTest.java:30)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
一行日志引起了注意
"GET-DATA-13" #13 prio=5 os_prio=31 tid=0x00007fa31811d800 nid=0x3e03 waiting on condition [0x0000700003e65000]
java.lang.Thread.State: WAITING (parking)
...
at java.util.concurrent.FutureTask.get(FutureTask.java:191)
at jstack.JstackTest$1.call(JstackTest.java:30)
很明显“GET-DATA”阻塞在了FutureTask的get方法,对照业务代码JstackTest.java:30, 发现对应位置是将一个异步逻辑放到线程池中执行,并等待结果。 进一步排查代码发现,JstackTest 本身提交来一个异步任务,并且异步任务内部又提交了一个任务,并且使用的同一个线程池,简化后的demo代码如下:
public class JstackTest {
private ExecutorService executorService = Executors.newFixedThreadPool(1);
public void test() throws ExecutionException, InterruptedException {
Future<Object> outerFuture = executorService.submit(() -> {
Future<Object> innerFuture = executorService.submit(() -> {
// do something
return null;
});
innerFuture.get();
return null;
});
outerFuture.get();
}
}
从上面的demo可以看到,outerFuture会等待innerFuture的处理结果,当线程池的工作线程都在处理outerFuture的时候,innerFuture只能被扔到LinkedBlockingQueue中等待执行。从而形成了死锁。
找到问题,解决就很简单了。
- 设置Future.get的超时时间
- outerFuture和innerFuture做线程池隔离,使用各自独立的线程池
回到最开始的问题,怎么回答“你知道线程有几种状态吗?状态之间的扭转是怎样的?”呢?
线程有五种状,分别是***** , 通过new、wait、notify、sychroniezd、lock等操作进行状态扭转,一般线上问题排查中经常会遇到线程处于Waiting/Blocked状态,比如有一次怎么怎么的
立马就体现出你的线上故障排查能力。
这是我在网上找的一个岗位要求(第6条)
1、统招本科及以上学历,三年以上的Java开发经验;
2、熟练掌握OO思想,具备扎实的抽象编程、设计能力及良好的单元测试习惯;
3、熟练掌握MySQL、Mongo、Redis的开发使用并了解各结构的性能;
4、熟悉Linux平台常用操作命令及具有编写脚本的能力;
5、熟悉Spring、Mybatis、Zookeeper、dubbo等常用开源项目;
6、熟练掌握多线程编程、JVM内存管理、类加载机制等;熟练掌握Java系统的故障排查和性能调优技能;
7、具有高日千万级以上PV电商业务架构、社交平台开发者优先录用;
8、具备较强的学习能力和责任心,良好的沟通能力、文档编写能力、有github开源项目者优先。
觉得还不错的话,留个👍让我看到你!!