Создание одного небезобезопасного объекта в потоке и использование случается до гарантии

Я хочу использовать классы SOAPConnectionFactory и MessageFactory из SAAJ с несколькими потоками, но оказывается, что я не могу предположить, что они потокобезопасны. Некоторые связанные должности:

Вот интересное небольшое доказательство того, что он может быть потокобезопасным: http://svn.apache.org/repos/asf/axis/axis2/java/core/tags/v1.5.6/modules/saaj/src/org/apache/axis2/saaj/SOAPConnectionImpl.java сказано

Несмотря на то, что спецификации безопасности SAAJ явно не требуются, похоже, что SOAPConnection в реализации ссылок Sun является потокобезопасной.

Но все же я не считаю достаточным доказательство, чтобы рассматривать классы SAAJ как потокобезопасные.

Итак, мой вопрос: верно ли идиома ниже? Я создаю ровно один объект SOAPConnection и MessageFactory, используя, возможно, непоточные безопасные фабрики внутри основного потока, а затем безопасно публиковать этот объект в задаче исполнителя, используя гарантию завершения интерфейса CompletionService. Я также использую это, прежде чем гарантировать извлечение результата объекта 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 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() гарантирует, что задача лямбда увидит полные объекты, а .take() гарантирует, что основной поток будет видеть только полностью сформированные ответы.

Как правило, вам не нужно это делать. static factory методы всегда должны быть потокобезопасными, потому что нет способа гарантировать, что они не используются в каком-либо другом потоке без глобального знания всей JVM, и вы не можете писать код, который полагается на это в многие среды. Иногда вы увидите реализацию, которая может иметь проблему, если один поток пытается использовать ее, а другой настраивает ее, но даже это редко.

Представьте себе сервлет, который использует SOAPConnectionFactory. Невозможно узнать, что в одном JVM нет другого веб-приложения, которое также не использует его одновременно, поэтому оно должно быть потокобезопасным.

Итак, действительно, MessageFactory.newInstance() и SOAPConnectionFactory.newInstance() были бы ошибочными, если бы они не были потокобезопасными. Я бы использовал их в нескольких потоках, не беспокоясь, и просто проверьте источник, если вы действительно обеспокоены. Но на самом деле все в порядке.

С другой стороны, объекты (даже другие фабрики), созданные статическими методами factory, часто не являются потокобезопасными, и вы не должны предполагать, что они без документации, которая так говорит. Даже проверки источника недостаточно, потому что если интерфейсы не документированы как потокобезопасные, то кто-то может добавить небезопасное состояние в реализацию позже.

Ответ 2

Я потратил час на обнаружение источников com.sun.xml.internal.messaging.saaj (используется как реализация SAAJ по умолчанию в Oracle JDK), и выяснил, что ни один из фабрики, возвращенные WhateverFactory.newInstance(), имеют какое-либо внутреннее состояние. Таким образом, они определенно потокобезопасны и не требуют одновременного создания нескольких экземпляров.

Эти заводы:

Например, HttpSOAPConnectionFactory эффективно имеет всего 3 строки в теле:

public class HttpSOAPConnectionFactory extends SOAPConnectionFactory {

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

Как насчет SOAPMessage и SOAPConnection - они должны использоваться в одном потоке, хотя операции с ними связаны с несколькими вызовами. (На самом деле, SOAPConnection#call() также является потокобезопасным, поскольку HttpSOAPConnection не содержит никакого внутреннего состояния, кроме переменной closed., но не следует использовать, если вы не гарантируете, что .close() никогда не вызывается, иначе последует .call().) После завершения обработки SOAPConnection должен быть закрыт и забыт, а также SOAPMessage экземпляры, используемые в конкретном цикле запросов-ответов.

В заключение: я считаю, что вы все делаете правильно, за исключением создания отдельных заводов для каждого вызова. По крайней мере, в упомянутой реализации эти заводы полностью потокобезопасны, поэтому вы можете сэкономить на загрузке классов.


Все сказанное относится к реализации SAAJ по умолчанию, которая поставляется с Oracle JDK. Если вы используете коммерческий сервер приложений Java EE (Websphere, JBoss и т.д.), Где реализация может быть специфичной для поставщика, лучше обратиться к вашему вопросу с их помощью.

Ответ 3

Я проверил ваш код. Похоже, вы создаете soapConnection через soapConnectionFactory, который отлично подходит. Следующий метод в SAAJ 1.3 возвращает новый экземпляр MessageFactory

public static MessageFactory newInstance(String protocol) throws SOAPException {
    return SAAJMetaFactory.getInstance().newMessageFactory(protocol);
}

В описании безопасности потоков нет информации, но, просмотрев код, кажется, что этот метод использует главным образом переменные стека, например. имеет объект SOAPConnection в стеке и использует его. Я не вижу проблемы, если soapConnection.call(request, example.com) вызывается несколькими потоками, несмотря на отсутствие синхронизированных блоков.

Можно было бы ожидать, что потоки отправят свое сообщение результата через разные соединения