最近遇到个面试题:
“
一个线程发生异常以后,线程状态会怎么样?
今天, 来做个实验,解答下这个问题.
准备环节
首先是要熟悉一下线程状态概念,以及线程状态查看方法.
Java 里的线程有哪些状态
线程是Java里的调度单元, 随着调度的进行, 可以有若干种状态, 可以按上图转化. 不过READY 和 RUNNING 是操作系统线程才会细分的状态,Java里的线程不分这个.
如何获取线程状态
-
对于独立的线程, 只需要调用 Thread.getState()
即可获取线程状态. -
对于执行器内线程, 需要用反射获取线程池,再获取状态.
各种执行器工厂,返回的都是ThreadPoolExecutor
的一个实例. 若要获取其内部的线程池,主要是获取一个workers
属性.老规矩,还是用joor
来做反射.代码需要这么写.
ExecutorService single = Executors.newSingleThreadExecutor();
single.submit(() -> { });
List<Thread.State> states = on(on(single).<Object>get("e"))
.<Set<Object>>get("workers").stream()
.map(worker -> on(worker).<Thread>get("thread"))
.map(Thread::getState)
.collect(Collectors.toList());
System.out.println(states);
别的也类似, 具体要看装饰类的结构.
实验代码
准备3种不同的线程,来试一下,到底会怎么样?
-
野生线程, 直接new出来那种 -
守护线程 -
固定线程数线程池执行器内的线程
普通野生线程里发生异常
可以准备一个最简单的1/0
代码,生成异常.
Thread exceptionThread = new Thread(() -> {
int n = 1/0;
});
exceptionThread.start();
logState(exceptionThread);
Thread.sleep(1000);
logState(exceptionThread);
这个最好猜测, 因为无论是否异常, 执行完都是TERMINATED
状态.验证了果然如此.
RUNNABLE
Exception in thread "Thread-0" java.lang.ArithmeticException: / by zero
at sh.now.afk.trysth.threadstate.Main.lambda$main$0(Main.java:18)
at java.base/java.lang.Thread.run(Thread.java:833)
TERMINATED
daemon线程的情况
守护线程指的是, 还有守护线程活着的情况下, JVM不会退出.跟线程本身执行完还活不活,没太大关系.
private static void daemonThread() throws InterruptedException {
Thread exceptionThread = new Thread(() -> {
int n = 1/0;
});
exceptionThread.setDaemon(true);
exceptionThread.start();
logState("daemon", exceptionThread);
Thread.sleep(1000);
logState("daemon", exceptionThread);
}
所以这代码不用猜就知道, 肯定是和前面没区别的.
单线程线程池的情况
这里实验代码写成这样.
private static void singleThread() throws InterruptedException {
ExecutorService single = Executors.newSingleThreadExecutor();
single.submit(()->{
int n = 1/0;
});
logState(single);
Thread.sleep(1000);
logState(single);
}
结果如下, 线程没变,甚至根本连异常都没抛出来.
[1919892312WAITING]
[1919892312WAITING]
原因也简单,执行器里的代码,是包装在FutureTask
里包装执行的.
内部的异常已经被抓住了, 业务异常并不会影响到线程池执行器里线程的状态.那这样,其他复杂情况估计就不用看了.
执行器怎么获取异常
刚刚已经知道,执行器里用户代码的异常并不会往外泄漏. 那么,异常怎么处理?
什么都不用做.FutureTask
本身是会把异常存储起来,放到outcome
字段的. 在FutureTask.get()
调用过程中,会通过report
函数自动把异常丢出来.
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
执行器的提交和执行过程
实验到这其实就可以结束了. 执行器内部异常报错,并不会影响线程池内部的线程.相反,获取异常反而需要些额外的手段.
不过就写这么点有点短, 再来补充下提交一个任务给执行器以后, 都有什么流程吧.
-
Executor
只有一个方法execute(Runnable)
. -
ExecutorService
额外提供了两个: -
submit(Runnable)
Runnable
会使用RunnableAdapter
包装成Callable
再变成FutureTask
-
submit(Callable)
Callable
会作为参数用FutureTask
包装 -
FutureTask
的run
里面, 会try
住执行异常并把结果和异常都保存下来 -
FutureTask
的get
可以获取结果或者异常
附:如何打印出线程的所有状态
好像还是有点短,加一个代码吧. 下面这堆可以把一个线程经历的几种状态都给造出来。
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) throws InterruptedException{
CountDownLatch latch = new CountDownLatch(1);
Thread thread = new Thread(() -> {
synchronized (Main.class) {
System.out.println("在原生锁里面");
}
try {
latch.await(3, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
while (true) {
try {
latch.await();
break;
} catch (InterruptedException e) {
System.out.println("interrupted, state=" + Thread.currentThread().getState());
}
}
System.out.println("内部获得锁之后的状态=" + Thread.currentThread().getState());
});
synchronized (Main.class) {
System.out.println("刚创建" + thread.getState());
thread.start();
System.out.println("启动后" + thread.getState());
Thread.sleep(1000);
System.out.println("阻塞了一会以后" + thread.getState());
}
Thread.sleep(1000);
System.out.println("头把锁未超时前" + thread.getState());
thread.interrupt();
Thread.sleep(1000);
System.out.println("倒数前" + thread.getState());
latch.countDown();
System.out.println("倒数后" + thread.getState());
Thread.State state;
while ((state = thread.getState()) == Thread.State.WAITING) ;
System.out.println("倒数后状态变了" + state);
Thread.sleep(1000);
System.out.println("最后的状态" + thread.getState());
}
}
后话
今天又是水水的一天。全文重点就几个字:执行器内有try,异常不会外泄。不用记得这个答案, 如何检查线程状态才是重点。
今天是端午节,喜逢佳节,祝大家端午节快乐。
原文始发于微信公众号(K字的研究):一个线程发生异常时候,线程是什么状态?
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/29705.html