Что мне следует синхронизировать в критических разделах Java?

StackOverflow https://stackoverflow.com/questions/416183

Вопрос

В Java идиоматический способ объявления критических разделов в коде следующий:

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Почти все блоки синхронизируются this, но есть ли для этого особая причина?Есть ли другие возможности?Существуют ли какие-либо рекомендации относительно того, какой объект синхронизировать?(например, частные случаи Object?)

Это было полезно?

Решение

Во-первых, обратите внимание, что следующие фрагменты кода идентичны.

public void foo() {
    synchronized (this) {
        // do something thread-safe
    }
}

и:

public synchronized void foo() {
    // do something thread-safe
}

делать точно то же самое.Никаких предпочтений ни к одному из них, кроме читабельности кода и стиля.

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

Также обратите внимание, что существуют ситуации, в которых вам может понадобиться синхронизация на стороне клиента блоки кода, в которых запрашивается монитор (т.синхронизированный объект) не обязательно this, как в этом примере:

Vector v = getSomeGlobalVector();
synchronized (v) {
    // some thread-safe operation on the vector
}

Я предлагаю вам получить больше знаний о параллельном программировании. Это вам очень пригодится, если вы точно будете знать, что происходит за кулисами.Вам следует проверить Параллельное программирование на Java, отличная книга на эту тему.Если вы хотите быстро погрузиться в тему, посмотрите Параллелизм Java @ Sun

Другие советы

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

Особенно неприятный случай возникает, если вы выберете синхронизацию с java.lang.String . Строки могут быть (и на практике почти всегда) интернированы. Это означает, что каждая строка одинакового содержания - в ENTIRE JVM - оказывается за кулисами одной и той же строкой. Это означает, что если вы синхронизируете какую-либо строку, другой (полностью несопоставимый) фрагмент кода, который также блокирует строку с тем же содержимым, на самом деле также заблокирует ваш код.

Однажды я устранял проблему взаимоблокировки в производственной системе и (очень болезненно) отследил ее до двух совершенно разрозненных пакетов с открытым исходным кодом, каждый из которых синхронизировался на экземпляре String, содержимое которого было " LOCK " .

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

public class Foo {
    private final Object syncObject = new Object();
    …
}

Теперь я могу использовать этот объект для синхронизации, не боясь никого & # 8220; воровать & # 8221; замок.

Просто для того, чтобы подчеркнуть, что в Java есть также ReadWriteLocks, которые можно найти как java.util.concurrent.locks.ReadWriteLock.

В большинстве случаев я разделяю блокировку на «для чтения» и «для обновлений». Если вы просто используете синхронизированное ключевое слово, все чтения в одном и том же блоке метода / кода будут «поставлены в очередь». Только один поток может получить доступ к блоку одновременно.

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

Поэтому блокировка чтения / записи имеет больше смысла для меня во время многопоточного программирования.

Вы хотите синхронизировать объект, который может служить мьютексом. Если текущий экземпляр (ссылка this ) подходит (например, не Singleton), вы можете использовать его, так как в Java любой объект может служить мьютексом.

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

Это во многом зависит от среды, в которой вы работаете, и типа системы, которую вы строите. В большинстве приложений Java EE, которые я видел, в действительности нет необходимости в синхронизации ...

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

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

В последнем случае я использую простой объект:

private Object mutex=new Object();

Однако, увидев много дампов JVM и следов стека, которые говорят, что блокировка является "экземпляром java.lang.Object ()" Я должен сказать, что использование внутреннего класса часто может быть более полезным, как предлагали другие.

В любом случае, это стоит моих двух битов.

Редактировать. Еще одна вещь: при синхронизации с this я предпочитаю синхронизировать методы и сохранять методы очень детальными. Я думаю, что это понятнее и лаконичнее.

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

Использование другой ссылки специально для блокировки путем объявления и инициализации частного поля. Object lock = new Object() например, это то, что мне никогда не было нужно и что я не использовал.Я думаю, что это полезно только тогда, когда вам нужна внешняя синхронизация двух или более несинхронизированных ресурсов внутри объекта, хотя я всегда стараюсь реорганизовать такую ​​ситуацию в более простую форму.

В любом случае, неявный (синхронизированный метод) или явный synchronized(this) часто используется, в том числе в библиотеках Java.Это хорошая идиома, и, если она применима, она всегда должна быть вашим первым выбором.

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

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

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

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

  

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

Это объявление синхронизирует весь метод.

private synchronized void doSomething() {

Это объявление синхронизировало часть блока кода вместо всего метода.

private void doSomething() {
  // thread-safe code
  synchronized(this) {
    // thread-unsafe code
  }
  // thread-safe code
}

Из документации оракула страница

синхронизация этих методов имеет два эффекта:

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

  

Есть ли другие возможности? Есть ли лучшие рекомендации по тому, на каком объекте синхронизироваться? (например, частные экземпляры Object?)

Есть много возможностей и альтернатив синхронизации. Вы можете сделать ваш поток кода безопасным, используя высокоуровневый параллелизм API (доступно с версии JDK 1.5)

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

Для получения более подробной информации см. ниже вопросы SE.

Синхронизация против блокировки

Не использовать синхронизацию (это) в Java?

Рекомендуется создавать объект исключительно для обеспечения блокировки:

private final Object lock = new Object();

private void doSomething() {
  // thread-safe code
  synchronized(lock) {
    // thread-unsafe code
  }
  // thread-safe code
}

Делая это, вы можете быть уверены, что ни один вызывающий код не сможет заблокировать ваш метод непреднамеренной синхронизированной (yourObject) строкой.

( Кредиты @jared и @ yuval-adam, которые объяснили это более подробно выше. )

Я предполагаю, что популярность использования this в учебных пособиях пришла из ранних версий Sun javadoc: https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top