为每个线程创建一个非线程安全对象并使用 happens-before 保证
Creating one non-thread-safe object per thread and using happens-before guarantee
我想将 SAAJ 中的 SOAPConnectionFactory 和 MessageFactory 类 与多个线程一起使用,但事实证明我不能假设它们是线程安全的。
一些相关帖子:
- javax.xml.soap.MessageFactory's instance is thread-safe?
- jaxp object caching for MessageFactory
这是一个有趣的小证明,它可以是线程安全的:
http://svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.5.6/modules/saaj/src/org/apache/axis2/saaj/SOAPConnectionImpl.java
据说
Although thread safety is not explicitly required by the SAAJ specs, it appears that the SOAPConnection in Sun's reference implementation is thread safe.
但我仍然认为这不足以证明 SAAJ 类 是线程安全的。
所以我的问题是:下面的成语正确吗?我使用主线程内可能非线程安全的工厂创建了一个 SOAPConnection 和 MessageFactory 对象,然后使用 CompletionService 接口的 happens-before 保证将这些对象安全地发布到执行程序任务。我也使用这个 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;
}
我测试了您的代码,您似乎正在通过 soapConnectionFactory 创建 soapConnection,这非常好。 SAAJ 1.3 中的以下方法 returns MessageFactory
的新实例
public static MessageFactory newInstance(String protocol) throws SOAPException {
return SAAJMetaFactory.getInstance().newMessageFactory(protocol);
}
没有描述线程安全的信息,但是从代码来看,这个方法似乎主要使用堆栈变量,例如。在堆栈中有一个 SOAPConnection 对象并使用它。尽管没有同步块,但如果 soapConnection.call(request, "example.com") 被多个线程调用,我看不出问题。
人们会期望线程会通过不同的连接发送它们的结果消息
我花了一个小时来发现 com.sun.xml.internal.messaging.saaj
的来源(用作 Oracle JDK 中的默认 SAAJ 实现)并发现 none [=12] 返回的工厂=] 有任何内部状态。所以它们绝对是线程安全的,不需要多次实例化。
这些工厂是:
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
变量之外不保存任何内部状态。它可能,但是 应该not 被重用,除非你保证 .close()
永远不会被调用,否则后续的 .call()
将抛出。) 处理完成后,应该关闭 SOAPConnection
并被遗忘以及 SOAPMessage
在特定请求-响应周期中使用的实例。
总而言之:除了为每个调用创建单独的工厂外,我相信您所做的一切都是正确的。至少在提到的实现中这些工厂是完全线程安全的,所以你可以节省加载 类.
所说的都是指 Oracle JDK 附带的默认 SAAJ 实现。如果您使用商业 Java EE 应用程序服务器(Websphere、JBoss 等),其中实施可能是特定于供应商的,最好将您的问题提交给他们的支持。
是的,您关于 CompletionService 的推理是正确的——.submit() 确保任务 lambda 将看到完整的对象,而 .take() 确保主线程将只看到完整的响应。
不过,通常您不需要这样做。 static 工厂方法应该始终是线程安全的,因为有 no 方法可以确保它们不在没有全局知识的其他线程中使用整个 JVM 的一部分,并且在许多环境中您无法真正编写依赖于它的代码。有时,如果一个线程尝试使用它而另一个线程正在配置它,您会看到一个实现可能有问题,但即使这样也很少见。
想象一个使用 SOAPConnectionFactory 的 servlet。不可能知道同一个 JVM 中没有其他 Web 应用程序没有同时使用它,因此它必须是线程安全的。
所以,实际上,如果 MessageFactory.newInstance() 和 SOAPConnectionFactory.newInstance() 不是线程安全的,它们就是错误的。我会毫不担心地在多个线程中使用它们,如果您真的很担心,只需检查源代码。但他们真的很好。
另一方面,由静态工厂方法创建的对象(甚至其他工厂)通常不是线程安全的,您不应该假设它们没有这样说明的文档。即使检查源代码也是不够的,因为如果接口没有被记录为线程安全的,那么以后有人可以将不安全状态添加到实现中。
我想将 SAAJ 中的 SOAPConnectionFactory 和 MessageFactory 类 与多个线程一起使用,但事实证明我不能假设它们是线程安全的。 一些相关帖子:
- javax.xml.soap.MessageFactory's instance is thread-safe?
- jaxp object caching for MessageFactory
这是一个有趣的小证明,它可以是线程安全的: http://svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.5.6/modules/saaj/src/org/apache/axis2/saaj/SOAPConnectionImpl.java 据说
Although thread safety is not explicitly required by the SAAJ specs, it appears that the SOAPConnection in Sun's reference implementation is thread safe.
但我仍然认为这不足以证明 SAAJ 类 是线程安全的。
所以我的问题是:下面的成语正确吗?我使用主线程内可能非线程安全的工厂创建了一个 SOAPConnection 和 MessageFactory 对象,然后使用 CompletionService 接口的 happens-before 保证将这些对象安全地发布到执行程序任务。我也使用这个 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;
}
我测试了您的代码,您似乎正在通过 soapConnectionFactory 创建 soapConnection,这非常好。 SAAJ 1.3 中的以下方法 returns MessageFactory
的新实例public static MessageFactory newInstance(String protocol) throws SOAPException {
return SAAJMetaFactory.getInstance().newMessageFactory(protocol);
}
没有描述线程安全的信息,但是从代码来看,这个方法似乎主要使用堆栈变量,例如。在堆栈中有一个 SOAPConnection 对象并使用它。尽管没有同步块,但如果 soapConnection.call(request, "example.com") 被多个线程调用,我看不出问题。
人们会期望线程会通过不同的连接发送它们的结果消息
我花了一个小时来发现 com.sun.xml.internal.messaging.saaj
的来源(用作 Oracle JDK 中的默认 SAAJ 实现)并发现 none [=12] 返回的工厂=] 有任何内部状态。所以它们绝对是线程安全的,不需要多次实例化。
这些工厂是:
SOAPConnectionFactory
-- client.p2p.HttpSOAPConnectionFactoryMessageFactory
-- 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
变量之外不保存任何内部状态。它可能,但是 应该not 被重用,除非你保证 .close()
永远不会被调用,否则后续的 .call()
将抛出。) 处理完成后,应该关闭 SOAPConnection
并被遗忘以及 SOAPMessage
在特定请求-响应周期中使用的实例。
总而言之:除了为每个调用创建单独的工厂外,我相信您所做的一切都是正确的。至少在提到的实现中这些工厂是完全线程安全的,所以你可以节省加载 类.
所说的都是指 Oracle JDK 附带的默认 SAAJ 实现。如果您使用商业 Java EE 应用程序服务器(Websphere、JBoss 等),其中实施可能是特定于供应商的,最好将您的问题提交给他们的支持。
是的,您关于 CompletionService 的推理是正确的——.submit() 确保任务 lambda 将看到完整的对象,而 .take() 确保主线程将只看到完整的响应。
不过,通常您不需要这样做。 static 工厂方法应该始终是线程安全的,因为有 no 方法可以确保它们不在没有全局知识的其他线程中使用整个 JVM 的一部分,并且在许多环境中您无法真正编写依赖于它的代码。有时,如果一个线程尝试使用它而另一个线程正在配置它,您会看到一个实现可能有问题,但即使这样也很少见。
想象一个使用 SOAPConnectionFactory 的 servlet。不可能知道同一个 JVM 中没有其他 Web 应用程序没有同时使用它,因此它必须是线程安全的。
所以,实际上,如果 MessageFactory.newInstance() 和 SOAPConnectionFactory.newInstance() 不是线程安全的,它们就是错误的。我会毫不担心地在多个线程中使用它们,如果您真的很担心,只需检查源代码。但他们真的很好。
另一方面,由静态工厂方法创建的对象(甚至其他工厂)通常不是线程安全的,您不应该假设它们没有这样说明的文档。即使检查源代码也是不够的,因为如果接口没有被记录为线程安全的,那么以后有人可以将不安全状态添加到实现中。