gpt4 book ai didi

java - CompletableFuture/ForkJoinPool 集合类加载器

转载 作者:IT老高 更新时间:2023-10-28 13:47:05 24 4
gpt4 key购买 nike

我解决了一个非常具体的问题,其解决方案似乎是基本的:

我的 (Spring) 应用程序的类加载器层次结构是这样的:SystemClassLoader -> PlatformClassLoader -> AppClassLoader

如果我使用 Java CompleteableFuture 来运行线程。线程的ContextClassLoader为:SystemClassLoader -> PlatformClassLoader -> ThreadClassLoader

因此,我无法访问 AppClassLoader 中的任何类,尽管我必须这样做,因为所有外部库类都驻留在那里。

源代码库非常大,所以我不想/不能将所有与线程相关的部分重写为其他内容(例如,将自定义执行程序传递给每个调用)。

所以我的问题是:我怎样才能使创建的线程例如CompleteableFuture.supplyAsync() 使用 AppClassLoader 作为父级?(而不是 PlatformClassloader)

我发现 ForkJoinPool用于创建线程。但在我看来,一切都是staticfinal。所以我怀疑即使设置自定义 ForkJoinWorkerThreadFactory在这种情况下,使用系统属性会有所帮助。还是会?

编辑以回答评论中的问题:

  • 你在哪里部署?这是否在 jetty/tomcat/任何 JEE 容器中运行?

    • 我使用的是默认的 Spring Boot 设置,因此使用了一个内部的 tomcat 容器。
  • 您遇到的具体问题是什么?

    • 确切的问题是:java.lang.IllegalArgumentException: org.keycloak.admin.client.resource.RealmsResource 从方法引用在类加载器中不可见
  • 您提交给 supplyAsync() 的作业是从 AppClassLoader 创建的,不是吗?

    • supplyAsync 是从使用 AppClassLoaderMainThread 调用的。但是,调试应用程序显示所有此类线程都将 PlatformClassLoader 作为其父级。据我了解,这是因为 ForkJoinPool.commonPool()在应用程序启动期间构建(因为它是静态的),因此使用默认的类加载器作为父类,即 PlatformClassLoader。因此,该池中的所有线程都将 PlatformClassLoader 作为 ContextClassLoader 的父级。 (而不是 AppClassLoader)。

    • 当我在 MainThread 中创建自己的执行程序并将此执行程序传递给 supplyAsync 时,一切正常 - 在调试过程中我可以看到现在确实 AppClassLoader 是我的 ThreadClassLoader 的父级。这似乎证实了我在第一种情况下的假设,即公共(public)池不是由 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]

最佳答案

我遇到了类似的事情,想出了一个不使用反射的解决方案,似乎与 JDK9-JDK11 配合得很好。

这里是javadocs说:

The parameters used to construct the common pool may be controlled by setting the following system properties:

  • java.util.concurrent.ForkJoinPool.common.threadFactory - the class name of a ForkJoinPool.ForkJoinWorkerThreadFactory. The system class loader is used to load this class.

因此,如果您推出自己的 ForkJoinWorkerThreadFactory 版本并将其设置为使用系统属性使用正确的 ClassLoader,这应该可以工作。

这是我的自定义 ForkJoinWorkerThreadFactory:

package foo;

public class MyForkJoinWorkerThreadFactory implements ForkJoinWorkerThreadFactory {

@Override
public final ForkJoinWorkerThread newThread(ForkJoinPool pool) {
return new MyForkJoinWorkerThread(pool);
}

private static class MyForkJoinWorkerThread extends ForkJoinWorkerThread {

private MyForkJoinWorkerThread(final ForkJoinPool pool) {
super(pool);
// set the correct classloader here
setContextClassLoader(Thread.currentThread().getContextClassLoader());
}
}
}

然后在你的应用启动脚本中设置系统属性

-Djava.util.concurrent.ForkJoinPool.common.threadFactory=foo.MyForkJoinWorkerThreadFactory

上述解决方案的工作原理是,当第一次引用 ForkJoinPool 类并初始化 commonPool 时,此线程的上下文 ClassLoader 是您需要的正确的(而不是 System类加载器)。

这里有一些 background这可能会有所帮助:

Fork/Join common pool threads return the system class loader as their thread context class loader.

In Java SE 9, threads that are part of the fork/join common pool will always return the system class loader as their thread context class loader. In previous releases, the thread context class loader may have been inherited from whatever thread causes the creation of the fork/join common pool thread, e.g. by submitting a task. An application cannot reliably depend on when, or how, threads are created by the fork/join common pool, and as such cannot reliably depend on a custom defined class loader to be set as the thread context class loader.

由于上述向后不兼容的变化,使用以前在JDK8中工作的ForkJoinPool的东西在JDK9+中可能无法工作。

关于java - CompletableFuture/ForkJoinPool 集合类加载器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/49113207/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com