java - 想要并行运行非线程安全库 - 可以使用多个类加载器来完成吗?

标签 java multithreading classloader

我在一个项目中工作,我们使用的库在 Java 8 流场景中不能保证线程安全(也不是)和单线程,它可以按预期工作。

我们希望使用并行流来获得低垂的可扩展性成果。

不幸的是,这会导致库失败 - 很可能是因为一个实例干扰了与另一个实例共享的变量 - 因此我们需要隔离。

我正在考虑为每个实例(可能是线程本地)使用单独的类加载器,据我所知,这应该意味着我获得了所需的隔离,但我不熟悉为此目的故意构建类加载器。

这是正确的方法吗?我该如何做才能获得适当的生产质量?


编辑:我被要求提供有关触发问题的情况的更多信息,以便更好地理解它。问题仍然是关于一般情况,而不是修复库。

我可以完全控制由库创建的对象(即 https://github.com/veraPDF/),因为

<dependency>
    <groupId>org.verapdf</groupId>
    <artifactId>validation-model</artifactId>
    <version>1.1.6</version>
</dependency>

使用项目 Maven 存储库获取工件。

<repositories>
    <repository>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
        <id>vera-dev</id>
        <name>Vera development</name>
        <url>http://artifactory.openpreservation.org/artifactory/vera-dev</url>
    </repository>
</repositories>

目前加固库是不可行的。


编辑:我被要求显示代码。我们的核心适配器大致是:

public class VeraPDFValidator implements Function<InputStream, byte[]> {
    private String flavorId;
    private Boolean prettyXml;

    public VeraPDFValidator(String flavorId, Boolean prettyXml) {
        this.flavorId = flavorId;
        this.prettyXml = prettyXml;
        VeraGreenfieldFoundryProvider.initialise();
    }

    @Override
    public byte[] apply(InputStream inputStream) {
        try {
            return apply0(inputStream);
        } catch (RuntimeException e) {
            throw e;
        } catch (ModelParsingException | ValidationException | JAXBException | EncryptedPdfException e) {
            throw new RuntimeException("invoking VeraPDF validation", e);
        }
    }

    private byte[] apply0(InputStream inputStream) throws ModelParsingException, ValidationException, JAXBException, EncryptedPdfException {
        PDFAFlavour flavour = PDFAFlavour.byFlavourId(flavorId);
        PDFAValidator validator = Foundries.defaultInstance().createValidator(flavour, false);
        PDFAParser loader = Foundries.defaultInstance().createParser(inputStream, flavour);
        ValidationResult result = validator.validate(loader);

        // do in-memory generation of XML byte array - as we need to pass it to Fedora we need it to fit in memory anyway.

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        XmlSerialiser.toXml(result, baos, prettyXml, false);
        final byte[] byteArray = baos.toByteArray();
        return byteArray;
    }
}

这是一个从 InputStream(提供 PDF 文件)映射到字节数组(表示 XML 报告输出)的函数。

(看到代码,我注意到构造函数中有对初始化程序的调用,在我的特定情况下,这可能是罪魁祸首。我仍然想要一个通用问题的解决方案。

最佳答案

我们也面临过类似的挑战。问题通常来自于不情愿地在各个线程之间“共享”的静态属性。

只要我们可以保证静态属性实际上是在由我们的类加载器加载的类上设置的,使用不同的类加载器就对我们有用。 Java 可能有一些类提供在线程之间不隔离或不是线程安全的属性或方法('System.setProperties()Security. addProvider() 没问题 - 顺便说一句,欢迎任何有关此问题的规范文档)。

一个潜在可行且快速的解决方案 - 至少可以让您有机会为您的库测试这个理论 - 是使用 Servlet 引擎,例如 Jetty 或 Tomcat。

构建一些包含您的库的 war 并并行启动进程(每场 war 1 个)。

当在 servlet 线程中运行代码时,这些引擎的 WebappClassLoaders 会首先尝试从父类加载器(与引擎相同)加载类,如果找不到该类,尝试从与 war 一起打包的 jars/classes 中加载它。

使用 jetty,您可以以编程方式将 war 热部署到您选择的上下文中,然后在理论上根据需要扩展处理器( war )的数量。

我们通过扩展 URLClassLoader 实现了自己的类加载器,并从 Jetty Webapp ClassLoader 中获得灵感。这并不像看起来那么难。

我们的类加载器完全相反:它尝试从本地的 jar 中加载一个类到“包”first,然后尝试从父类加载器中获取它们。这保证了永远不会考虑(首先)由父类加载器意外加载的库。我们的“包”实际上是一个 jar,其中包含带有自定义 list 文件的其他 jar/库。

“按原样”发布此类加载器代码没有多大意义(并会产生一些版权问题)。如果您想进一步探索那条路线,我可以尝试制作一个骨架。

Jetty WebappClassLoader 的来源

关于java - 想要并行运行非线程安全库 - 可以使用多个类加载器来完成吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41936609/

相关文章:

java - 如何修改 Eclipse 插件中的函数体?

java - GlassFish 扩展通用类加载器使用的 JAR

java - 为什么 Log4J2 查找文件名中带有类加载器字符串的配置文件?

java - ClassLoader 应该是线程安全的吗?

objective-c - 如何在 NSOperationQueues 上自动安装 NSExceptionHandlers?

java - Android - 如何找出当前线程的创建位置?

java - HBase-创建表时出错

java - Android 金融 API

java - JetBrains 的 @Contract 注释

c++ - std::queue 是否具有事件机制(std::queue 中的信号)