我想使用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;
}
答案 0 :(得分:7)
是的,你对CompletionService的推理是正确的 - .submit()确保任务lambda将看到完整的对象,而.take()确保主线程只能看到完全形成的响应。
但是,一般情况下,您不需要这样做。 static 工厂方法应该始终是线程安全的,因为有 no 方法可以确保它们在没有全局JVM的全局知识的情况下不在其他线程中使用,并且你不能真正编写在许多环境中依赖于它的代码。有时,如果一个线程试图使用它而另一个线程正在配置它,你会看到一个可能有问题的实现,但是,即使这种情况很少见。
想象一下使用SOAPConnectionFactory的servlet。不可能知道同一个JVM中没有其他Web应用程序同时没有使用它,所以它必须是线程安全的。
所以,实际上,MessageFactory.newInstance()和SOAPConnectionFactory.newInstance()如果它们不是线程安全的,那就错了。我会毫不担心地在多个线程中使用它们,如果您真的担心的话,请检查源代码。但他们真的很好。
另一方面,静态工厂方法创建的对象(甚至是其他工厂)通常不是线程安全的,您不应该假设它们没有说明的文档。即使检查源是不够的,因为如果没有记录接口是线程安全的,那么有人可以在以后向实现添加不安全状态。
答案 1 :(得分:4)
我花了一个小时发现com.sun.xml.internal.messaging.saaj
的源代码(用作Oracle JDK中的默认SAAJ实现),发现WhateverFactory.newInstance()
返回的工厂都没有任何内部状态。所以它们绝对是线程安全的,不需要多次实例化。
这些工厂是:
SOAPConnectionFactory
- client.p2p.HttpSOAPConnectionFactory MessageFactory
- soap.ver1_1.SOAPMessageFactory1_1Impl 例如,HttpSOAPConnectionFactory
实际上只有3行:
public class HttpSOAPConnectionFactory extends SOAPConnectionFactory {
public SOAPConnection createConnection() throws SOAPException {
return new HttpSOAPConnection();
}
}
SOAPMessage
和SOAPConnection
怎么样 - 它们必须在一个线程中使用,尽管对它们进行的操作涉及多个调用。 (事实上,SOAPConnection#call()
也是线程安全的,因为HttpSOAPConnection
除了closed
变量之外没有任何内部状态。它可能,但不应该可以重复使用,除非你保证永远不会调用.close()
,否则后续的.call()
将会抛出。)处理完成后,SOAPConnection
应该被关闭并被遗忘{ {1}}在特定请求 - 响应周期中使用的实例。
总结:我相信除了为每次通话创建单独的工厂外,你都能正确地完成所有工作。至少在提到的实现中,这些工厂是完全线程安全的,因此您可以节省加载类。
所有声明都指的是Oracle JDK附带的默认SAAJ实现。如果您使用商业Java EE应用程序服务器(Websphere,JBoss等),其中实现可能是特定于供应商的,那么最好将您的问题提交给他们的支持。
答案 2 :(得分:2)
我测试了你的代码,看起来你正在通过soapConnectionFactory创建一个soapConnection,这非常好。 SAAJ 1.3中的以下方法返回MessageFactory的新实例
public static MessageFactory newInstance(String protocol) throws SOAPException {
return SAAJMetaFactory.getInstance().newMessageFactory(protocol);
}
关于线程安全性的描述中没有信息,但是通过查看代码,似乎该方法主要使用堆栈变量,例如在堆栈中有一个SOAPConnection对象并使用它。如果没有同步块,尽管soapConnection.call(request,&#34; example.com&#34;)被多个线程调用,我看不到问题。
可以预期线程会通过不同的连接发送结果消息