В критических разделах Java, что я должен синхронизировать?


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

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

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

9 65

9 ответов:

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

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

и:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

private Object mutex=new Object();

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

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

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

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

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

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

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

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

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

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

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

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

private synchronized void doSomething() {

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

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

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

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

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

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

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

Lock objects
Executors
Concurrent collections
Atomic variables
ThreadLocalRandom

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

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

избегайте synchronized (this) в Java?