Squeak.ru - шаблоны программирования

Создание одного не потокобезопасного объекта на поток и использование гарантии «происходит до»

Я хочу использовать классы 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'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, чтобы уменьшить количество экземпляров и повторно использовать их в не мешающих задачах? 22.03.2016
  • Просто интересно: почему вы используете статическую основную сеть для тестирования; а не модульные тесты? 22.03.2016
  • @SashaSalauyou Да, на самом деле экземпляр для каждой задачи. Но я не думаю, что это сильно меняет. Теоретически я мог бы создать объект внутри отправки и как бы кэшировать его внутри ThreadLocal для случая, когда другая задача выполняется в том же потоке, но мне все равно придется вызывать по крайней мере SOAPConnectionFactory.newInstance() внутри блока кода задачи, который здесь предполагается, что он не является потокобезопасным. Дайте мне знать, если я что-то упустил. Кроме того, я не забочусь об их повторном использовании. Вызов службы для моей ситуации занимает около 1 минуты, поэтому скорость создания объектов не является узким местом. 22.03.2016
  • @Jägermeister, это не тестовый код, это упрощенный производственный код. 22.03.2016
  • @Ruslan, если вы знаете о небезопасности потоков .newInstance(), вы можете явно синхронизировать фабрику в ThreadLocal.withInitial(). Для 100 экземпляров вы не увидите разницы, но повторное использование в продакшене будет более эффективным. 22.03.2016
  • Явная синхронизация @SashaSalauyou — это то, чего я стараюсь избегать в первую очередь. Потому и задал вопрос :) Для проверки описанной выше идиомы. И, поверьте мне или нет, меня совершенно не волнует повторное использование этих объектов :-) У меня есть очень особый сценарий, который выходит за рамки моего поста. 22.03.2016
  • @SashaSalauyou Вы говорите, что моя идиома верна в одном комментарии, а затем, что она неверна в другом комментарии :) Что касается вашего последнего комментария: идиома пытается решить именно это. Он использует общие объекты внутри основного потока, а не внутри потоков задач. 22.03.2016

Ответы:


1

Да, ваши рассуждения о CompletionService верны — .submit() гарантирует, что лямбда задачи увидит полные объекты, а .take() гарантирует, что основной поток увидит только полностью сформированные ответы.

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

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

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

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

30.03.2016
  • Отличный ответ! Мне также нравится обсуждение статических фабрик. Тем не менее, не могли бы вы предоставить несколько доказательств, подтверждающих, что небезопасная для потоков статическая фабрика является ошибкой? Я думаю, что в основном понимаю ваше объяснение, но просто хочу убедиться, что ничего не упускаю, и это действительно общепринятый способ мышления. 30.03.2016
  • Ссылки на доказательства? А, нет, извините. Я занимаюсь подобными вещами в течение долгого времени, поэтому я не проверяю свои рассуждения против случайных людей в Интернете. Не стесняйтесь снимать галочку, если это вас беспокоит. Если вы хотите быть действительно уверены, проверьте исходный код. 30.03.2016
  • Такое ощущение, что мне не хватает какого-то общего понятия или идиомы. Вот почему я попросил вас привести некоторые доказательства. Обычно ребята из Java ссылаются на «Эффективную Java» или «Параллелизм Java на практике». Я пытался найти в Интернете хотя бы случайного человека, объясняющего, что непоточно-безопасная статическая фабрика — это ошибка, но безуспешно. Так вот, просто хотел убедиться, что это правда. И проверка исходного кода — это то, чего я старался избегать в первую очередь как наименее безопасный метод. 30.03.2016
  • Во многих случаях, когда вы пропустите основную информацию, будет невозможно найти такие прямые подтверждения. Иногда лучшее, что вы можете сделать, это найти пример кода, написанный людьми, которые должны знать о нем, и вывести из него неустановленные свойства интерфейса. Например, если вы погуглите примеры SOAPConnectionFactory, вы найдете много кода, который был бы неправильным, если бы .newInstance() не был потокобезопасным. Часть из них будет от Oracle или Sun. Со временем вы узнаете, как авторы таких вещей ожидают, что их классы будут использоваться, даже если они не объясняют это. 01.04.2016

  • 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 и т. д.), где реализация может зависеть от поставщика, лучше обратиться с вопросом в их поддержку.

    25.03.2016
  • В любом случае, я до сих пор не могу понять вашего намерения использовать абстракцию CompletionService. Все, что вы делаете, может быть легко сделано Future<...> f = executor.submit(() -> { ... }). Все эти Future вы можете собрать в список, затем брать по одному и вызывать f.get() на каждом, чтобы получить результаты каждой задачи. Future#get() возвращает гарантированно после того, как исполнитель обработает задачу и вернет результат. 26.03.2016
  • f.get() для одной задачи заблокирует и помешает мне вызвать f.get() для задачи, которая уже завершена. Вот для чего существует CompletionService. Это дает мне результаты по мере их поступления. 28.03.2016
  • @Руслан да ты прав. Теперь я понимаю. Я полагал, вам нужно их заказать. 28.03.2016
  • Ответ не отвечает на мой вопрос. Я не спрашивал, являются ли эти фабрики потокобезопасными или нет. Я уже изучил вопрос и пришел к выводу, что нет достаточных доказательств, чтобы рассматривать их как потокобезопасные. Только явные документы могут доказать это в общем виде. См. Java Concurrency In Practice, чтобы узнать, как обрабатывать такие случаи отсутствия документации. 30.03.2016
  • Я настоятельно рекомендую вам прочитать соответствующую главу в Java Concurrency In Practice, в которой объясняется, как поступать в таких ситуациях. Во-первых, код может измениться в любое время, и сопровождающим не нужно обеспечивать безопасность потоков. Во-вторых, как вы сказали, есть и другие реализации. Но в любом случае книга дает еще несколько соображений. 30.03.2016

  • 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") вызывается несколькими потоками, несмотря на отсутствие синхронизированных блоков.

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

    25.03.2016
  • Это не ответ на мой вопрос. Я не спрашивал, являются ли эти фабрики потокобезопасными или нет. Я уже провел свое расследование и пришел к выводу, что нет достаточных доказательств, чтобы рассматривать их как потокобезопасные. Только явные документы могут доказать это в общем виде. См. Java Concurrency In Practice, чтобы узнать, как обрабатывать такие случаи отсутствия документации. 30.03.2016
  • Новые материалы

    Угловая структура архитектуры
    Обратите внимание, что эта статья устарела, я решил создать новую с лучшей структурой и с учетом автономных компонентов: https://medium.com/@marekpanti/angular-standalone-architecture-b645edd0d54a..

    «Данные, которые большинство людей используют для обучения своих моделей искусственного интеллекта, поставляются со встроенным…
    Первоначально опубликовано HalkTalks: https://hacktown.com.br/blog/blog/os-dados-que-a-maioria-das-pessoas-usa-para-treinar-seus-modelos-de-inteligencia-artificial- ja-vem-com-um-vies-embutido/..

    Сильный ИИ против слабого ИИ: различия парадигм искусственного интеллекта
    В последние годы изучению и развитию искусственного интеллекта (ИИ) уделяется большое внимание и прогресс. Сильный ИИ и Слабый ИИ — две основные парадигмы в области искусственного интеллекта...

    Правильный способ добавить Firebase в ваш проект React с помощью React Hooks
    React + Firebase - это мощная комбинация для быстрого и безопасного создания приложений, от проверки концепции до массового производства. Раньше (знаете, несколько месяцев назад) добавление..

    Создайте API с помощью Python FastAPI
    Создание API с помощью Python становится очень простым при использовании пакета FastAPI. После установки и импорта вы можете создать приложение FastAPI и указать несколько конечных точек. Каждой..

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

    Получить бесплатный хостинг для разработчиков | Разместите свой сайт за несколько шагов 🔥
    Статические веб-сайты — это веб-страницы с фиксированным содержанием и его постоянным содержанием. Но теперь статические сайты также обрабатывают динамические данные с помощью API и запросов...


    © 2024 squeak.ru, Squeak.ru - шаблоны программирования