Java线程的异常处理机制

前言

启动一个Java程序,本质上是运转某个Java类的main办法。咱们写一个死循环程序,跑起来,然后运转jvisualvm进行调查

Java线程的反常处理机制

能够看到这个Java进程中,一共有11个线程,其中10个守护线程,1个用户线程。咱们main办法中的代码,就跑在一个名为main的线程中。当Java进程中跑着的一切线程都是守护线程时,JVM就会退出线程池原理

在单线程的场景下,如果代码运转到某个方位时抛出了反常,会看到控制台打印出反常的仓库信息。

但在jvm面试题多线程的场景下,子线程中发生的反常,不一定就能及时的将反常信息打印出来。

我曾经在作业中遇到过一次,采用Compljava怎么读etableFuture.runAsync异步处理耗时使命时,使命处理进程中出现反常,然而日志中没有任何关于反常的信息。

时隔许久,从头温习了线程中的反常处理机制,加深字节码了对线程作业jvm原理原理的了解,特此记录。

线程的反常处理机制

咱们开源软件知道,Java程序的运转,是先经由javac将Java源代码编译成class字节码文件,然后由JVM加载并解析cljavascriptass文件,随后从主类的main办法开始履行。

当一个线程在运转进程中抛出了未捕获线程池的七个参数反常时,会由JVM调用这个线程目标上的dispatchUncaughtExce开源矿工ption办法,进行反常处理。

// Thread类中
private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
}

源码很好了解,先获取一个UncaughtExceptionHandl线程池原理er反常处理器,然后经过调用这个反常处理器的uncaughtException办法来对反常进行处理。(下文用缩写ueh来表明UncaughtExceptionHandler

uehjavascript是个 啥呢?其实便是定义在Thread内部的一个接口,用作反常处理。

    @FunctionalInterface
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

再来看下Thread目标中的getUncaughtExceptionHandler办法

	public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }

java面试题检查当时字节码文件这个线程池原理Thread目标是否有设置字节码文件扩展名自定义的ueh目标,若有,则由其对反jvm垃圾回收机制常进行处理,不然,由当时Thread目标所属的线程组(ThreadGroupjvm是什么)进行反常处理。咱们点开源码开源节流,简略发线程池的工作原理ThreJVMadGroup类本身完成了Thread.UncaughtExceptionHandler接口,也便是jvm内存模型ThreadGroup本身便是个反常处理器。

public class ThreadGroup implements Thread.UncaughtExceptionHandler {
    private final ThreadGroup parent;
    ....
}

假设咱们在main办法中抛出一个反常,若没有对majava培训in线程设置自定义的ueh目标,则交由main线程所属的ThreadGroup来处理反常。咱们看下Threjava环境变量配置adGroup是怎开源矿工样处理反常的:

    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread ""
                                 + t.getName() + "" ");
                e.printStackTrace(System.err);
            }
        }
    }

这部分源码也比较简略。首先是检查当时ThreadGroupjvm内存模型否具有父级的Thjava怎么读readGr开源代码网站githuboup,若有,则调用父级ThreadGroup进行反常处理。不然,调用静态办法Thread.get开源软件DefaultUncaughtExceptionHandler()获取一个默许ueh目标。

默许ue线程池面试题h目标不为jvm优化空,则由这个开源阅读默许的ueh目标进行反常处理;不然,当反常不是ThreadDeath时,直接将当时线程的姓名,和反常的仓库信息,经过标准错误输出System.err)打印到控制台。

咱们随便运转一个main办法,看一下线程的开源阅读情况

Java线程的反常处理机制

Java线程的反常处理机制

能够看到,main线程归于一个同样名为mainThreadGroup,而这个mainThreadGroup,其父级ThreadGroup名为sjvm垃圾回收机制ystem,而这个systemThreadGroupjava编译器,没有父级了,它便是根Th开源中国readGroup

由此可知,main线程中抛出的未捕获反常,终究会交由名为systemThreadGroup进行反常处理,而由于没有设置默许ueh目标,反常信息会经过Sjava培训ystem.err输出到控制台。

接下字节码文件扩展名来,咱们经过最朴素的办法(new一个Thr开源节流ead),在main线程中创立一个子线程,在子线程中编写能抛出反常的代码,进jvm是什么行调查

    public static void main(String[] args)  {
        Thread thread = new Thread(() -> {
            System.out.println(3 / 0);
        });
        thread.start();
    }

Java线程的反常处理机制

子线程中的反字节码是什么意思常信息被打印到了控制台。反常处理的流程便是咱们上面描绘的那样。

小结

所以,正常来说,如果没有对某个线程设置特定的ueh目标;也没有调用静态办法Thread.setDefaultUncaughtExceptio字节码是什么意思nHandler设置线程池的使用大局默许ueh目标。那么,在任意一个线程的运转进程中抛出未捕获反常时,反常信息都会被输出到控制台(当反常是ThreadDeath时则不会进行jvm是什么意思输出,但一般来说,反常都不是ThreadDeath,不过这个细节要留意下)。

如何设置自定义的ueh目标来进行反常处理?依据上面的剖析可知,有2种办法

  • 对某一个Thread目标,调用其setUncaughtExceptionHandler办法,设置一个ueh目标。留意这个ueh目标只对这个线程起作用
  • 调用静jvm调优态办法Thread.setDefaultUncaughtExceptionHandler()设置一个大局默许ueh目标。这样设置的ueh目标会对一切线程起作用

当然,由于ThreadGroup本身能够充任ueh,所java怎么读以其实还能java环境变量配置字节码完成一个ThreadGroup子类,重写其uncaughtEx字节码是什么意思ception办法进行反常处理。

若一个线程没有进行任何设置,当在这个线程内抛出反常后,默许会将线程名称和反常仓jvm是什么意思库,经过Systejava培训m.err进行输出。

线程的反常处理机制,用一个流程图表明如下

Java线程的反常处理机制

线程池字节码场景下的反常处理

在实践的开发中,咱们经常会运用线程池来进行多线程的管理和控制,而不是经过new来手动创立java编译器Thread目标。

对于Java中的线程池ThreadPoolExecutjvm是什么意思or,咱们知道,一般来说有两种办法,能够向线程池提交使命:

  • execute
  • submi线程池有哪几种t

其中execute办法没有回来值,咱们经java怎么读exejvm优化cute提交的使命,只需求提交该使命给线程池履行,开源节流是什么意思而不需求获取使命的履行成果。

submit办法,会回来一个Future字节码是什么意思标,咱们经过submit提交的使命,能够开源代码网站github经过这个Future目标,拿到使命的履行成果。

咱们分别测验如下代码

    public static void main(String[] args)  {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        threadPool.execute(() -> {
            System.out.println(3 / 0);
        });
    }
    public static void main(String[] args)  {
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        threadPool.submit(() -> {
            System.out.println(3 / 0);
        });
    }

简略得到如下成果:

经过execute办法提交的使命,反常信息被打印到控制台;经过submit办法提交的使命,没有出现反常信息。

咱们略微跟一下Thjvm是什么readPoolExecujava怎么读tor的源码,当运用execute办法提交使命时,在runWorker办法中,会履行到下图红java环境变量配置框的部分

Java线程的反常处理机制

Java线程的反常处理机制

在上面的代码履行结束后,由于反常线程池创建throw了出来,所以会由JVM捕捉到,并调用当时子线程dispatjvm原理chUncaughtException办法进行处理,依据上面的剖析,终究反常仓库会被打印到控制台。

多扯几句别的。

上面跟源码时,留意到W开源是什么意思orkerThreadPoolExecutor的一个内部类,也便是说,每个Worker都会隐式的持有ThreadPoolExecutor目标的引证jvm内存(内部类的相关原理请自行jvm调优补课)。字节码文件每个Worker在运转时(在不同的子线程中运转)都能够对T字节码是什么意思hreadPoolExecutor目标(一般来说这开源中国个目标是在main线程中被维护)中的属性进行拜访和修正。Wjava环境变量配置orker完成了Runnable接口,并java编译器且其run办法实践是调用的ThreadPoolExecutor上的runWorker办法。在新建一个WJVMorker时,会创立一java怎么读个新的Thread目标,并jvm性能调优把当时Worker的引证传递给这个Threjavaeead目标,随后调用这个Thread目标的start开源矿工办法,则开始在这个Thread中(子线程中)运转这个Work开源代码网站githuber

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

ThreadPoolExecutor中的addWorker办法

Java线程的反常处理机制

再次跟源码时,加深了对ThreadPoolExecutorJavaWorker体系的了解和java面试题知道。

jvm是什么意思们之间有一种嵌套依赖的联系。每个Worker里持有一个Thread目标,这个Threajvm调优d目标又是以这个jvm性能调优Worker目标作为Runnable,而Worker又是ThreadPoolEjvm是什么意思xecutor的内部类,这意味着每个Worker目标都会隐式的持有其所属的ThreadPoolExecutor目标的引证。每个Workerrun办法, 都跑在子线程中,但是这些Worker跑在子线程中时,能够对ThreadPoolExecutor目标的属性进行拜访和修正(每个线程池的工作原理Workerrun办法都是调用的runWorker,所以runWorker办法是跑在子线程中的,这个办法中会对线程池的状况进行拜访和修正,比方当时子线程运转进程中抛出反常时,会从ThreadPoolExecutor中移除当时Worker,并开源节流启一个新的Worjavascriptker)。而一般来说,ThreadPoolExecutor目标的引证,咱们一般是在主线程字节码中进行维护的。

反正便是这中心其实有点骚东西,没那么简略。需求多跟几次源码,多自己打断点进行debug,debug进程中能够经过IDEA的Evaluate Expression功用实时调查当时办法履行时所在的线程环境(Thread.开源众包currentThread)。

扯得有点远了,现在回到正题。上面说了调用ThreadPoolExecutor中的execute办法提交使命,子线程中出现反常时,反常会被抛出,打印在控制台,并且当时Worker会被线程池收回,并重启一个新的Worker作为代替。

那么,调用submit时,反常为何就没有被打印到控制台呢?

咱们看一下源码

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
        return new FutureTask<T>(runnable, value);
    }

经过调用submit提交的使命,被包装了成了一个FutureTask目标,随后会将这个Fujvm是什么tureTask目标,经过execjvm调优ute办法提交给线程池,并回来FutureTask目标给主线程的调用者。

也便是说,submit办法实践做了这几件事

  • 将提交的Runnable,包装成FutureTask
  • 调用execute办法提交这个FutureTask(实践还是经过execute提交的使命)
  • FutureTask作为回来值,回来给主线程的调用者

要害就开源众包在于FutureTask,咱们来看一下

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    // Executors中
	public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

经过submit办法传入的Runnable,经过一个适配器RunnableAdapter转化为了Callable目标,并终究包装成为一个FutureTask目标。这个FutureTask,又完成了Runnabl开源阅读eFuture接口

Java线程的反常处理机制

所以咱们看下FutureTaskrun办法(由于终究是将包装后的FutureTask提交给线程池履行,所以终究会履行FutureTaskrun办法)

Java线程的反常处理机制

    protected void setException(Throwable t) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = t;
            UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
            finishCompletion();
        }
    }

能够看到,反常信息仅仅被简略的设置到了FutureTaskoutcome字段上。并没有往外抛,所以这儿其实相当于把jvm性能调优反常给生吞了catch块中捕捉到开源阅读app下载安装反常后,既没有打印反常的仓库,也没有把反常持续往外throw。所以咱们无法在控制台看到反常信息,在实践的项目中,此种场景下的反常信息也不会被输出到日志文件。这一点要特别留意,会加大问题的排查难度。

那么,为什么要这样处理呢?

由于咱们经过submit提交使命时,会拿到一个Future目标

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

咱们能够在稍后,经过Future目标,来获悉使命的履行情况,包含使命是否成功履jvm面试题行结束,使命履行后回来的成果是什么,履行进程中是否出现反常。

所以,经过submit提交的使命,实践会把使命的各种状况信息,都封装在FutureTask目标中。当最后调用FutureTjava模拟器ask目标上的get办法,测验获取使命履行成果时,才能够看到反常信息被打印出来。

    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    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); // 反常会经过这一句被抛出来
    }

小结

  • 经过ThreadPoolExecutorexecute办法提交的使命,出现反常后,反常会在子java是什么意思线程中被抛出,线程池的使用并被JVM捕获,并调用子线程的d线程池原理ispatchUncaughtException办法,进行反常处理,若子线程没有任何特别设置,则反常仓库会被输出到System.err,即反常会被打印到控制台上。并且会从线程池中移除当时Worker,并另启一个新的Worker作为字节码文件扩展名代替。
  • 经过ThreadPoolExecutorsubmit办法提交的使命,使命会先被包装成FutureTask目标,出现反常后,反常会被生吞,并暂存到FutureTask目标中,作为使命履行成果的一部分。反常信息不会被打印jvm性能调优该子线程池原理线程也不会被线程池移除(由于反常在子线程开源节流中被吞了,没有抛出来)。在调用FutureTask上的g开源众包et办法时(此时一般是在主线程中了),反常才会被抛出,触发主线程的反常处理,并输出到System.err

其他

其他的线程池场景。比方

  • 运用ScheduledThreadPoolExecutor完成推迟使命或者守时使命开源阅读周期使命)开源软件javaee剖析进程也是相似。这儿给字节码是什么意思个简略定论,当调用scheduleAtFixedRate办法履行一个周期使命时(使命会被包装成Fujvm调优tureJVMTask (实践是ScheduledFutureTask ,是JVMFutureTask 的子类)),若周期使命中出现反常,反常会被生吞,反常信息不会被打印,线程不会被收回线程池创建,但是周期使命履行这一次jvm是什么意思后就不会持续履行了。ScheduledThreadPoolExec字节码utorjava面试题承了ThreadPoolExec开源是什么意思utor,所以其也是复用了ThreadPoolExecutor的那一套逻辑。
  • 运用CompletableFuture runAsync 提交使命,底层是经过Fork线程池的工作原理JoinPool 线程开源阅读app下载安装jvm内存进行履行,使命会被包装成AsyncRu开源矿工n ,且会回来一个CompletableFu线程池的七个参数tu线程池有哪几种re 给主线程开源阅读。当使命出现反常时,处java是什么意思理办法和ThreadPooljava语言Executorsubmit 相似,反常仓库不会被打印。只有在CompletableFujvm性能调优ture 上调用get 办法测验获取成果开源中国时,反常才会被打印。

发表评论

提供最优质的资源集合

立即查看 了解详情