В критических разделах Java, что я должен синхронизировать? - PullRequest
70 голосов
/ 06 января 2009

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

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

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

Ответы [ 10 ]

64 голосов
/ 13 января 2009

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

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

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

46 голосов
/ 06 января 2009

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

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 Concurrency @ Sun

39 голосов
/ 06 января 2009

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

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

Теперь я могу использовать этот объект для синхронизации, не опасаясь, что кто-нибудь «украдет» замок.

6 голосов
/ 06 января 2009

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

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

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

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

4 голосов
/ 15 января 2009

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

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

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

private Object mutex=new Object();

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

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

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

4 голосов
/ 06 января 2009

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

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

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

2 голосов
/ 06 января 2009

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

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

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

1 голос
/ 23 июня 2010

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

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

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

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

0 голосов
/ 10 мая 2019

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

private final Object lock = new Object();

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

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

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

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

0 голосов
/ 19 апреля 2016

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

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

private synchronized void doSomething() {

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

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

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

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

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

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

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

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

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

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

Избежать синхронизации (этого) в Java?

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