异常情况

在springboot项目启动后,调用某个接口时产生如下报错:

java.lang.RuntimeException: 
Socket Factory class not found: 
java.lang.ClassNotFoundException: 
Class org.apache.hadoop.net.StandardSocketFactory not found

代码片段如下:

CompletableFuture.runAsync(() -> {
    queryInventorySinkToHdfs(); // 这行的具体方法报错
});

然后就开始下面的排查过程


确认class是否存在

在bin目录下找到相应的jar包

ForkJoinPool异步线程引起的ClassNotFoundException问题_线程池

之后解压查看

// 调用命令进行解压lib到当前目录下
jar xf schedule-task.jar BOOT-INF/lib/

ForkJoinPool异步线程引起的ClassNotFoundException问题_线程池_02

确认StandardSocketFactory.class 存在,排除 jar包版本问题

类加载器

这时候我们开始怀疑类加载器的问题

打断点在IDEA进行远程debug:

ForkJoinPool异步线程引起的ClassNotFoundException问题_类加载器_03

到报错处发现:

ForkJoinPool异步线程引起的ClassNotFoundException问题_主线程_04

这时候我们应该察觉到一些异常

正常来讲,我们的 springBoot 项目的类加载器应该为 LaunchedURLClassLoader,同时在 Tomcat 嵌入后,加载器会变成其子类 TomcatEmbeddedWebappClassLoader,而不应该是 AppClassLoader

问题的结果已经明了,我们来探寻下为什么类加载器会发生变化?

ForkJoinPool线程

在主线程提交任务后,我们理所当然的认为执行任务的线程会继承主线程的 上下文类加载器(context class loader)

CompletableFuture.runAsync(() -> {
    queryInventorySinkToHdfs();
});

但是上面的问题说明并不是,我们在去掉异步后发现其类加载器变为了 TomcatEmbeddedWebappClassLoader,同时报错消失。

这就说明了主线程在提交任务到ForkJoinPool 线程池时,任务线程并没有继承主线程的 上下文类加载器(context class loader)

我们结合 源码来看下:

CompletableFuture.runAsync()方法如下:

ForkJoinPool异步线程引起的ClassNotFoundException问题_ClassNotFound_05

继续找到这个异步线程池:

一般为多个,即调用的是ForkJoinPool.commonPool()

ForkJoinPool异步线程引起的ClassNotFoundException问题_主线程_06

这时候来到 ForkJoinPool 里面:

ForkJoinPool异步线程引起的ClassNotFoundException问题_主线程_07

进一步看下 common:

发现是在静态代码块里初始化的

ForkJoinPool异步线程引起的ClassNotFoundException问题_主线程_08

继续看下这个初始化过程:

ForkJoinPool异步线程引起的ClassNotFoundException问题_主线程_09

没有看到指定类加载器的过程

所以会沿用默认的上下文类加载器 AppClassLoader。在大多数Java应用中,系统类加载器是默认的类加载器,用于加载应用程序的类和资源。

解决方案

手动设置类加载器

获取主线程的类加载器之后,在每次任务执行时手动设置

// 获取当前线程的类加载器
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

CompletableFuture.runAsync(() -> {
    // 在新线程中设置类加载器
    Thread.currentThread().setContextClassLoader(classLoader);
    queryInventorySinkToHdfs();
});

采用自定义线程池

线程池在初始化的时候设置下线程的类加载器即可

最终问题得到解决,同时也进一步了解了类加载等知识。