为每个线程创建一个非线程安全对象,并使用“发生前”保证

我想将SAAJ的SOAPConnectionFactory和MessageFactory类与多个线程一起使用,但事实证明,我不能假设它们是线程安全的。一些相关帖子:

这里有一个有趣的小证明,证明它可以是线程安全的:http://svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.5.6/modules/saaj/src/org/apache/axis2/saaj/SOAPConnectionImpl.java 据说

尽管 SAAJ 规范没有明确要求线程安全,但 Sun 的引用实现中的 SOAPConnection 似乎是线程安全的。

但我仍然不认为这足以证明SAAJ类是线程安全的。

所以我的问题:下面的成语是正确的吗?我使用主线程内可能的非线程安全工厂创建一个 SOAPConnection 和 MessageFactory 对象,然后使用 CompleteService 接口的 happen-before 保证将这些对象安全地发布到执行器任务。我还使用这种发生之前保证来提取结果HashMap对象。

基本上,我只想验证我推理的合理性。

public static void main(String args[]) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(10);
    CompletionService<Map<String, String>> completionService = new ExecutorCompletionService<>(executorService);

    //submitting 100 tasks
    for (int i = 0; i < 100; i++) {
        // there is no docs on if these classes are thread-safe or not, so creating them before submitting to the
        // external thread. This seems to be safe, because we are relying on the happens-before guarantees of the
        // CompletionService.
        SOAPConnectionFactory soapConnectionFactory = SOAPConnectionFactory.newInstance();
        SOAPConnection soapConnection = soapConnectionFactory.createConnection();
        MessageFactory messageFactory = MessageFactory.newInstance();
        int number = i;// we can't just use i, because it's not effectively final within the task below
        completionService.submit(() -> {
            // using messageFactory here!
            SOAPMessage request = createSOAPRequest(messageFactory, number);
            // using soapConnection here!
            SOAPMessage soapResponse = soapConnection.call(request, "example.com");
            soapConnection.close();
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            soapResponse.writeTo(outputStream);
            // HashMap is not thread-safe on its own, but we'll use the happens-before guarantee. See f.get() below.
            Map<String, String> result = new HashMap<>();
            result.put("soapResponse", new String(outputStream.toByteArray()));
            return result;

        });
    }

    // printing the responses as they arrive
    for (int i = 0; i < 100; i++) {
        Future<Map<String, String>> f = completionService.take();
        Map<String, String> result = f.get();
        System.out.println(result.get("soapResponse"));
    }

    executorService.shutdown();
}

/**
 * Thread-safe static method
 */
private static SOAPMessage createSOAPRequest(MessageFactory messageFactory, int number) throws Exception {
    SOAPMessage soapMessage = messageFactory.createMessage();
    SOAPPart soapPart = soapMessage.getSOAPPart();

    String serverURI = "example.com";

    SOAPEnvelope envelope = soapPart.getEnvelope();
    envelope.addNamespaceDeclaration("example", serverURI);

    SOAPBody soapBody = envelope.getBody();
    SOAPElement soapBodyElem = soapBody.addChildElement("number", "example");
    soapBodyElem.addTextNode(String.valueOf(number));

    soapMessage.saveChanges();

    return soapMessage;
}

答案 1

是的,您对 CompletionService 的推理是正确的 -- .submit() 确保任务 lambda 将看到完整的对象,而 .take() 确保主线程仅看到完全形成的响应。

但是,通常不需要执行此操作。静态工厂方法应该始终是线程安全的,因为如果没有对整个JVM的全局了解,就无法确保它们不会在其他线程中使用,并且您无法真正编写在许多环境中依赖于此的代码。但是,有时您会看到一个实现,如果一个线程尝试使用它而另一个线程正在配置它,则可能会出现问题,但即使这样也很少见。

想象一个使用SOAPConnectionFactory的servlet。不可能知道在同一个 JVM 中没有其他 Web 应用程序同时使用它,因此它必须是线程安全的。

所以,真的,MessageFactory.newInstance()和SOAPConnectionFactory.newInstance()如果它们不是线程安全的,那就错了。我会在多个线程中使用它们而不必担心,如果您真的担心,只需检查源代码即可。但实际上他们很好。

另一方面,由静态工厂方法创建的对象(甚至是其他工厂)通常不是线程安全的,您不应该假设它们没有说明这一点的文档。即使检查源代码也是不够的,因为如果没有记录接口是线程安全的,那么有人可以在以后向实现中添加不安全状态。


答案 2

我花了一个小时来发现 com.sun..xml internal.messaging.saaj(在 Oracle JDK 中用作默认 SAAJ 实现)的来源,发现 返回的工厂中没有任何内部状态。因此,它们绝对是线程安全的,不需要多次实例化。WhateverFactory.newInstance()

这些工厂是:

例如,实际上在正文中只有 3 行:HttpSOAPConnectionFactory

public class HttpSOAPConnectionFactory extends SOAPConnectionFactory {

    public SOAPConnection createConnection() throws SOAPException {
        return new HttpSOAPConnection();
    }
}

和 -- 它们必须在一个线程中使用,尽管对它们执行的操作涉及多个调用。(事实上,SOAPConnection#call() 也是线程安全的,因为 HttpSOAPConnection 除了闭合变量之外不保存任何内部状态。它可以,但不应该重用,除非你保证永远不会调用.close(),否则后续的.call()将抛出。处理完成后,应关闭并忘记以及在特定请求 - 响应周期中使用的实例。SOAPMessageSOAPConnectionSOAPConnectionSOAPMessage

总而言之:我相信你做对了一切,除了为每个调用创建单独的工厂。至少在上述实现中,这些工厂是完全线程安全的,因此您可以节省加载类的时间。


所有这些都是指Oracle JDK附带的默认SAAJ实现。如果您使用商业Java EE应用程序服务器(Websphere,JBoss等),其中实现可能是特定于供应商的,那么最好将您的问题解决给他们的支持。


推荐