CompletableFuture / ForkJoinPool设置类加载器
我解决了一个非常具体的问题,它的解决方案似乎是基本的:
我(Spring)应用程序的类加载器层次结构是这样的: SystemClassLoader -> PlatformClassLoader ->
AppClassLoader
如果我使用Java CompleteableFuture
运行线程。该ContextClassLoader
线程的是:
SystemClassLoader -> PlatformClassLoader -> ThreadClassLoader
因此,AppClassLoader
尽管必须访问,但无法访问任何类,因为所有外部库类都驻留在该类中。
源代码库很大,因此我不想/不能将所有与线程相关的部分重写为其他内容(例如,将自定义执行程序传递给每个调用)。
所以我的问题是:
(而不是PlatformClassloader
)
我发现ForkJoinPool用于创建线程。但是在我看来,所有东西都是
静态的 和 最终的
。因此,我怀疑在这种情况下,即使使用系统属性设置自定义的ForkJoinWorkerThreadFactory也会有所帮助。还是会?
您部署到哪里? 是否在码头/ tomcat /任何JEE容器中运行?
- 我正在使用默认的Spring Boot设置,因此使用了一个内部tomcat容器。
您遇到的确切问题是什么?
确切的问题是:
您提交给supplyAsync()的作业是从AppClassLoader创建的,不是吗?
在
supplyAsync
从被称为MainThread
它使用AppClassLoader
。但是,调试应用程序表明,所有此类线程都具有PlatformClassLoader
其父级。据我了解,这是因为ForkJoinPool.commonPool()是在应用程序启动期间构造的(因为它是静态的),因此使用默认的类加载器作为父类PlatformClassLoader
。因此,该池中的所有线程均PlatformClassLoader
作为ContextClassLoader的父级(而不是AppClassLoader
)。当我在中创建自己的执行程序
MainThread
并将该执行程序传递给supplyAsync
所有可用的程序时,我可以在调试过程中看到确实现在AppClassLoader
是我的执行程序的父级ThreadClassLoader
。在第一种情况下,这似乎肯定了我的假设,即MainThread
至少在使用公共池时不会创建公共池AppClassLoader
。
java.lang.IllegalArgumentException: org.keycloak.admin.client.resource.RealmsResource referenced from a method is not visible from class loader at java.base/java.lang.reflect.Proxy$ProxyBuilder.ensureVisible(Proxy.java:851) ~[na:na]
at java.base/java.lang.reflect.Proxy$ProxyBuilder.validateProxyInterfaces(Proxy.java:682) ~[na:na]
at java.base/java.lang.reflect.Proxy$ProxyBuilder.<init>(Proxy.java:628) ~[na:na]
at java.base/java.lang.reflect.Proxy.lambda$getProxyConstructor$1(Proxy.java:426) ~[na:na]
at java.base/jdk.internal.loader.AbstractClassLoaderValue$Memoizer.get(AbstractClassLoaderValue.java:327) ~[na:na]
at java.base/jdk.internal.loader.AbstractClassLoaderValue.computeIfAbsent(AbstractClassLoaderValue.java:203) ~[na:na]
at java.base/java.lang.reflect.Proxy.getProxyConstructor(Proxy.java:424) ~[na:na]
at java.base/java.lang.reflect.Proxy.newProxyInstance(Proxy.java:999) ~[na:na]
at org.jboss.resteasy.client.jaxrs.ProxyBuilder.proxy(ProxyBuilder.java:79) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
at org.jboss.resteasy.client.jaxrs.ProxyBuilder.build(ProxyBuilder.java:131) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
at org.jboss.resteasy.client.jaxrs.internal.ClientWebTarget.proxy(ClientWebTarget.java:93) ~[resteasy-client-3.1.4.Final.jar!/:3.1.4.Final]
at org.keycloak.admin.client.Keycloak.realms(Keycloak.java:114) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]
at org.keycloak.admin.client.Keycloak.realm(Keycloak.java:118) ~[keycloak-admin-client-3.4.3.Final.jar!/:3.4.3.Final]
回答:
因此,这是一个非常肮脏的解决方案,我不为此感到骄傲,如果您继续使用它, 带来麻烦:
问题是该应用程序的类加载器未用于ForkJoinPool.commonPool()
。由于commonPool的设置是静态的,因此在应用程序启动期间,不容易(至少据我所知)以后进行更改。因此,我们需要依赖
。
在应用程序成功启动后创建一个钩子
- 就我而言(Spring Boot环境),这将是ApplicationReadyEvent
- 要收听此事件,您需要以下组件
@Component
class ForkJoinCommonPoolFix : ApplicationListener {
override fun onApplicationEvent(event: ApplicationReadyEvent?) {
}
}
在挂钩中,您需要将
ForkJoinWorkerThreadFactory
commonPool 设置为自定义实现(因此此自定义实现将使用应用程序类加载器)- 在科特林
val javaClass = ForkJoinPool.commonPool()::class.java
val field = javaClass.getDeclaredField(“factory”)
field.isAccessible = true
val modifiers = field::class.java.getDeclaredField(“modifiers”)
modifiers.isAccessible = true
modifiers.setInt(field, field.modifiers and Modifier.FINAL.inv())
field.set(ForkJoinPool.commonPool(), CustomForkJoinWorkerThreadFactory())
field.isAccessible = false
- 在科特林
简单实施
CustomForkJoinWorkerThreadFactory
- 在科特林
//Custom class
class CustomForkJoinWorkerThreadFactory : ForkJoinPool.ForkJoinWorkerThreadFactory {
override fun newThread(pool: ForkJoinPool?): ForkJoinWorkerThread {
return CustomForkJoinWorkerThread(pool)
}
}
// helper class (probably only needed in kotlin)
class CustomForkJoinWorkerThread(pool: ForkJoinPool?) : ForkJoinWorkerThread(pool)
- 在科特林
如果您需要
以及为什么更改最终字段不好,请参阅此处和此处。简短摘要:由于优化,更新的最终字段可能对其他对象不可见,并且可能发生其他未知的副作用。
就像问题本身所指出的那样:如果您仅在少数几个地方遇到此问题(即自行解决此问题本身就没有问题),则可以使用自己的Executor。从此处复制的一个简单示例:
ExecutorService pool = Executors.newFixedThreadPool(10);final CompletableFuture<String> future =
CompletableFuture.supplyAsync(() -> { /* ... */ }, pool);
以上是 CompletableFuture / ForkJoinPool设置类加载器 的全部内容, 来源链接: utcz.com/qa/407687.html