Вопрос параллелизма: синхронизированные методы - PullRequest
1 голос
/ 17 мая 2011

В ноябре 2009 года я пошёл в No Fluff Just Stuff. Одна из презентаций была сделана Брайаном Гетцем о параллелизме Java. По какой-то причине на слайде его повестки дня были пункты, которые не были освещены в его презентации.

Он рассмотрел некоторые стратегии и в конце указал на общую тактику, которую он также назвал хорошим практическим правилом для параллелизма в Java: сделайте ваши переменные частными и сделайте любые методы, которые обращаются к ним, синхронизированными.

Звучит довольно просто. И, возможно, слишком хорош, чтобы быть правдой. Существуют ли ситуации / приложения, где этот метод параллелизма был бы недостаточным? Хорошо ли будет полагаться в первую очередь на эту технику в системах с большим количеством транзакций или большими наборами данных? Каковы потенциальные недостатки этого?

Ответы [ 10 ]

4 голосов
/ 17 мая 2011

Существуют ли ситуации / приложения, в которых этого метода параллелизма было бы недостаточно?

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

Хорошо ли будет полагаться в первую очередь на этот метод в системах с большим количеством транзакций или большими наборами данных?

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

Каковы потенциальные недостатки этого?

Синхронизация не является бесплатной, существуют затраты (хотя обычно незначительные) с получением блокировок.Ненужная синхронизация означает ненужные затраты.

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

2 голосов
/ 17 мая 2011

Хотя это и плохо знакомо с параллельным программированием, я не думаю, что существует панацея, когда речь идет о параллелизме «сделайте все члены приватными, а методы синхронизированными».

Посмотрите на: http://download.oracle.com/javase/tutorial/essential/concurrency/index.html

Кроме того, вас могут заставить использовать синхронизированные методы, когда дело доходит до общих данных (данных, которые могут быть доступны и / или изменены несколькими потоками). В других случаях вам может не понадобиться использовать ключевое слово synchronized.

1 голос
/ 18 мая 2011

Яркий пример приведен в книге Гетца, на которую ссылается [matt b].

Перевод средств со счета A на B:

synchronized (a) {
    synchronized (b) {
        // do the transfer
    }
}

Кажется достаточно ясным, но что произойдет, если дваодновременно потоки пытаются перевести $ 1 из A в B, а другие $ 2 из B в A, и между синхронизированными блоками возникает временной интервал?

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

1 голос
/ 17 мая 2011

Это кажется серьезным перебором. Этого также не достаточно для последовательности приращения / получения, где другой поток потенциально может чередовать вызов set ().

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

1 голос
/ 17 мая 2011

Во многих случаях этого абсолютно недостаточно.Рассмотрим случай, когда вы хотите поменять положение двух элементов вектора (что в основном следует описанной тактике Гетца).Ваш код будет состоять из серии вызовов removeElement и insertElementAt (оба из которых синхронизированы).Однако, если объект Vector был изменен другим потоком между этими вызовами, результатом может быть полная чушь.

Хотя Гетц прав, что это обычная тактика, часто бывает, что более высокий уровеньсинхронизация нужна.Вот почему был представлен ArrayList - в основном это Vector без (часто ненужных) издержек синхронизации на уровне методов.

1 голос
/ 17 мая 2011

Помимо влияния на производительность, есть случаи, когда этот уровень защиты недостаточен. Например, setX (), setY (). Если ваш API имеет их отдельно, вы можете получить x1, y2, если один поток устанавливает x1, y1, а другой устанавливает x2, y2. Чтобы избежать этого, вам нужно заблокировать объект перед вызовом сеттеров или перепроектировать ваш API для поддержки setXY (x, y).

0 голосов
/ 22 мая 2011

Я почти уверен, что знаю, о какой презентации вы думаете, и вы пропустили довольно много шагов.

Совет, представленный в этой презентации: - Инкапсулируйте ваше состояние. (Nothing спорным здесь). - Если вам нужен синхронизированный доступ к состоянию, инкапсулируйте эту синхронизацию, то есть, это плохой призыв экспортировать требования синхронизации на ваших клиентах.

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

0 голосов
/ 19 мая 2011

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

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

Некоторые другие возможные проблемы, которые приходят на ум:

  • все члены закрыты и все методы синхронизированы?Включая сеттеры и геттеры?Это просто плохо, плохо, плохо.
  • как насчет тех случаев, когда производительность действительно критична, и ваши операции чтения блокируют / распознают как записи, так и другие операции чтения?Это также плохо, лучшая альтернатива - использовать что-то вроде ReentrantReadWriteLock.
  • подвержен тупикам, см. Пример предоставленного karmakaze

Но самое опасное в этом -получите это, как будто это был швейцарский армейский нож, когда это явно не так.Полезно помнить: синхронизация была частью Java с самого начала. Зачем тогда возиться со всеми альтернативами?Одна вещь должна быть ясна: не только по причинам производительности у нас есть все эти другие функции в настоящее время:

  • блокировка классов
  • множество параллельных классов
  • надлежащая изменчивость ихороший JMM с Java 1.5

Моя рекомендация?Постарайтесь узнать как можно больше о параллелизме в Java, есть много ресурсов, Google (или любая приличная SE) - ваш лучший друг.Просто будьте готовы потратить некоторое время, это не легко, и это может стать очень глубоким, если вы действительно хотите понять, что происходит в кроличьей норе.Современные и будущие центральные процессоры с постоянно растущим числом модулей выполнения потоков [сделают] навыки параллелизма «обязательными», поэтому нет причин для этого пропускать.

0 голосов
/ 17 мая 2011

Техника генерирует потокобезопасный код в том смысле, что защищает ваши java-переменные от прерываний чтения / записи из нескольких потоков. Однако это не означает, что ваш код правильный. Иногда синхронизация применяется к последовательностям выполнения, которые плохо представлены одним методом. Представьте себе подпрограмму, которая принимает прямоугольник и устанавливает ширину и длину, равные 5. Представьте себе другую подпрограмму в другом потоке, которая принимает прямоугольник и задает ширину, равную 3, и длину, равную 6. Даже если setWidth и setLength синхронизированы, нить 1 может устанавливать ширину, нить 2 может устанавливать ширину и длину, а нить 1 может устанавливать длину. Теперь прямоугольник имеет ширину 5 и длину 6. Это неверно для любой из нитей. Обратите внимание, что если бы прямоугольник был неизменным, эта конкретная проблема не возникла бы.

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

Теперь, ваше продолжение: каковы недостатки? Если у вас есть неизменный ресурс, вам, вероятно, нет необходимости защищать от многократного чтения разными потоками. Как следствие, дополнительные издержки кода синхронизации не нужны. Ваша программа, хотя и правильная, медленнее, чем другая правильная программа, реализованная с теми же алгоритмами только из-за ненужной синхронизации.

0 голосов
/ 17 мая 2011

«Сделайте ваши переменные приватными и синхронизируйте все методы, которые обращаются к ним».- это разумный совет сам по себе, и может сделать ваш код поточно-ориентированным при определенных базовых обстоятельствах.

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

Если вас интересует параллелизм и изменяемое состояние,Я настоятельно рекомендую посмотреть эту презентацию «Ценность, идентичность и состояние» Рича Хикки .В нем рассказывается о структуре системы параллелизма Clojure, но принципы важны и применимы к любому языку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...