当前位置: 首页 > 知识库问答 >
问题:

CompletableFuture/ForkJoinPool集类加载器

南宫星波
2023-03-14

我处理了一个非常具体的问题,其解决办法似乎是基本的:

我的(Spring)应用程序的类加载器层次结构如下所示:SystemClassLoader->PlatformClassLoader->AppClassLoader

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

源库相当大,所以我不想/不能将所有线程相关部分重写为其他内容(例如,向每个调用传递一个自定义执行器)。

所以我的问题是:如何使例如CompleteableFuture.SupplyAsync()创建的线程使用AppClassLoader作为父级?(而不是PlatformClassLoader)

我发现ForkJoinPool用于创建线程。但在我看来,那里的一切都是静止的和最终的。所以我怀疑在这种情况下,即使使用system属性设置自定义ForkJoinWorkerThreadFactory也会有所帮助。或者会吗?

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

>

  • 部署到哪里?是否在jetty/tomcat/any JEE容器中运行?

    • 我使用的是默认的Spring启动设置,因此使用的是内部tomcat容器。

    你到底有什么问题?

      null

    >

  • SupplyAsync从使用AppClassLoaderMainThread调用。但是,调试应用程序表明,所有此类线程都将PlatformClassLoader作为其父线程。根据我的理解,发生这种情况是因为ForkJoinPool.CommonPool()是在应用程序启动期间构造的(因为它是静态的),因此使用默认的类加载器作为父类,即PlatformClassLoader。因此,此池中的所有线程都将PlatformClassLoader作为ContextClassLoader(而不是AppClassLoader)的父线程。

    当我在MainThread中创建自己的执行器并将此执行器传递给SupplyAsync时,一切正常--而且我可以在调试期间看到,AppClassLoader实际上是我的ThreadClassLoader的父级。这似乎证实了我在第一种情况下的假设,即公共池不是由MainThread创建的,至少在它本身使用AppClassLoader时不是这样。

    完整StackTrace:

    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]
    
  • 共有1个答案

    茅星华
    2023-03-14

    我遇到了类似的情况,并想出了一个不使用反射的解决方案,似乎可以很好地与JDK9-JDK11一起工作。

    javadocs是这样说的:

    可以通过设置以下系统属性来控制用于构造公共池的参数:

      null
    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());
            }
        }
    } 
    

    然后在应用程序启动脚本中设置system属性

    -djava.util.concurrent.ForkJoinPool.Common.ThreadFactory=foo.MyForkJoinWorkerThreadFactory

    假设第一次引用ForkJoinPool类并初始化CommonPool时,此线程的上下文类加载器是您需要的正确的类加载器(而不是系统类加载器),上述解决方案就可以工作。

    以下是一些可能有所帮助的背景:

    fork/join公共池线程返回系统类加载器作为它们的线程上下文类加载器。

    在Java SE9中,作为fork/join公共池一部分的线程将始终返回系统类加载器作为其线程上下文类加载器。在以前的版本中,线程上下文类加载器可能是从导致fork/join公共池线程创建的任何线程继承的,例如通过提交任务。应用程序不能可靠地依赖于fork/join公共池何时或如何创建线程,因此不能可靠地依赖于将自定义的类加载器设置为线程上下文类加载器。

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

     类似资料:
    • 问题内容: 我解决了一个非常具体的问题,它的解决方案似乎是基本的: 我(Spring)应用程序的类加载器层次结构是这样的: 如果我使用Java 运行线程。该线程的是: 因此,尽管必须访问,但无法访问任何类,因为所有外部库类都驻留在该类中。 源代码库很大,因此我不想/不能将所有与线程相关的部分重写为其他内容(例如,将自定义执行程序传递给每个调用)。 所以我的问题是: 如何使通过创建的线程(例如,使用

    • 代码: 我有上面的代码来并行执行一些任务。考虑到已经让调用线程等待完成,不知道它是否应该是而不是块中的。 注意:仅从输入列表中读取。

    • 框架中所有的类都是通过类加载器(ClassLoader)加载的,通过Loader我们可以实现类的统一管理。下面我们一起来看看Loader提供了哪些加载方法: 1. Loader::import 加载一个类或者加载一个包 方法原型 import( $classPath, $type = IMPORT_APP, $extension=EXT_PHP ) 参数名称 参数说明 $classPath 文件的

    • 加载器,顾名思义,是用于加载元素的,加载的元素可以是库(类),视图文件 , 驱动器 ,辅助函数 , 模型 或其他你自己的文件。 注解 该类由系统自动加载,你无需手工加载。 应用程序"包" 包的视图文件 类参考 应用程序"包" 应用程序包(Package)可以很便捷的将你的应用部署在一个独立的目录中, 以实现自己整套的类库,模型,辅助函数,配置,文件和语言包。 建议将这些应用程序包放置在 appli

    • 如果我有一个内部类声明,例如: 其次是: A$B内部类也会加载吗?如果B内部类没有被声明为“静态”呢?

    • 我得到了新的笔记本电脑(Windows7),我正在尝试设置Maven。