为每个线程创建一个非线程安全对象并使用before-before保证

Rus*_*lan 14 java concurrency multithreading thread-safety saaj

我想使用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对象,然后使用CompletionService接口的before-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;
}
Run Code Online (Sandbox Code Playgroud)

Mat*_*ans 7

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

但是,一般情况下,您不需要这样做. 静态工厂方法应该始终是线程安全的,因为没有办法,以确保它们不是在没有整个JVM的全球知识的一些其他线程的使用,并且你不能真正写在许多环境中依赖于代码.有时,如果一个线程尝试使用它而另一个线程正在配置它,你会看到一个可能有问题的实现,但是,即使这种情况很少见.

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

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

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


Ale*_*you 5

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

这些工厂是:

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

public class HttpSOAPConnectionFactory extends SOAPConnectionFactory {

    public SOAPConnection createConnection() throws SOAPException {
        return new HttpSOAPConnection();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

总结:我相信除了为每个调用创建单独的工厂之外,您所做的一切都是正确的。至少在提到的实现中,这些工厂是完全线程安全的,因此您可以节省加载类的时间。


上述所有内容均指 Oracle JDK 附带的默认 SAAJ 实现。如果您使用商业 Java EE 应用服务器(Websphere、JBoss 等),其中的实现可能是特定于供应商的,那么最好向他们的支持人员提出您的问题。