一个线程发生异常时候,线程是什么状态?


最近遇到个面试题:

一个线程发生异常以后,线程状态会怎么样?

今天, 来做个实验,解答下这个问题.


准备环节

首先是要熟悉一下线程状态概念,以及线程状态查看方法.

一个线程发生异常时候,线程是什么状态?

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种不同的线程,来试一下,到底会怎么样?

  1. 野生线程, 直接new出来那种
  2. 守护线程
  3. 固定线程数线程池执行器内的线程

普通野生线程里发生异常

可以准备一个最简单的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包装
  • FutureTaskrun里面, 会try住执行异常并把结果和异常都保存下来
  • FutureTaskget可以获取结果或者异常

附:如何打印出线程的所有状态

好像还是有点短,加一个代码吧. 下面这堆可以把一个线程经历的几种状态都给造出来。

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/24756.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!