【发布时间】:2016-07-09 04:41:06
【问题描述】:
我想将 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 接口的happens-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;
}
【问题讨论】:
-
现在您创建的不是每个线程的实例,而是每个任务的实例(因此每个将创建 100 个实例)。为什么不使用
TreadLocal来减少实例化并在不干扰的任务中重用它们? -
只是想知道:为什么要使用静态 main 进行测试;而不是单元测试?
-
@SashaSalauyou 是的,实际上是每个任务的实例。但我认为变化不大。从理论上讲,我可以在“提交”中创建一个对象并将其缓存在 ThreadLocal 中,以防另一个任务碰巧在同一个线程中运行,但我仍然必须在任务代码中至少调用 SOAPConnectionFactory.newInstance()这里假定的块不是线程安全的。如果我遗漏了什么,请告诉我。另外,我不太关心“重用”它们。对于我的情况,服务调用大约需要 1 分钟,因此创建对象的速度不是瓶颈。
-
@Jägermeister 这不是“测试”代码,它是一种简化的生产代码。
-
@Ruslan 如果您知道
.newInstance()的线程不安全,您可以通过ThreadLocal.withInitial()中的工厂显式同步。对于 100 个实例,您不会看到差异,但在生产环境中重用会更有效。
标签: java multithreading concurrency thread-safety saaj