Java "синхронизируется" в классе объектов или в вызывающем классе

У меня есть многопоточная программа, и мне интересно, какой из способов использования "синхронизирован" правильный.

Способ 1: У меня есть объект класса (который будет передан нескольким потокам), который называется MyClass, внутри него:

public synchronized void set(String name) {
 this.name = name;
}

Способ 2: у меня есть тот же класс, но не имеющий "синхронизированного" в его методе набора:

public void set(String name) {
 this.name = name;
}

И вызывающий абонент сделает это:

MyClass myclass = new MyClass();
synchronized(myclass) {
 myclass.set("myclass");
}

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

6 ответов

Синхронизировать по методу:

  • Вы делаете это в одном месте,
  • Звонящим не нужно беспокоиться о синхронизации,
  • Вы не дублируете код везде, где вам нужно позвонить
  • Самое главное - если вызывающие абоненты опускают синхронизацию, это не сработает

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

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


Первый вариант - это правильная версия в целом.

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

Теперь технически в примере, который вы указали, нет никакого способа, чтобы два потока могли попасть в проблему, потому что, если один поток создает экземпляр и вызывает set(). Нет другого способа, которым поток мог иметь доступ к этому экземпляру. Поэтому никогда не будет споров о блокировке. Просто вы знаете, как создание объектов влияет на многопоточные программы. Помните, что два потока должны ОБРАТИТЬ общую ссылку на экземпляр, который они намереваются изменить для любых проблем с потоками, которые могут быть проблемой. Если они никогда не разделяют общую ссылку, нет проблем с безопасностью.


Я собираюсь голосовать за второй вариант. Классы вообще не должны быть потокобезопасными (и поэтому не должны быть внутренне синхронизированы). Безопасность требует времени и часто не нужна. Кроме того, вы часто получаете такие ситуации, как: if (list.isEmpty()) list.add( filler ); , Если оба метода синхронизируются внутри, это не делает ничего хорошего. Список может быть пустым при первом вызове и иметь 1 000 000 записей при add вызова. Весь оператор должен быть синхронизирован, чтобы быть полезным.

В более общем плане вам необходимо решить, для каждого класса, будь он потокобезопасным или нет. Те, что не будут, будут быстрее и могут быть сделаны потокобезопасными вызывающим абонентом, которому, как и в приведенном выше экземпляре, может понадобиться некоторая гибкость в том, как они идут по нему. Кроме того, есть умные способы избежать любой необходимости синхронизации, например, ссылаться на класс из одного потока. Большинство классов Java Collections Framework не являются и не должны быть потокобезопасными. Безопасность резьбы обычно должна обрабатываться на высоком уровне, а не на низком уровне.

Время от времени вы получаете класс низкого уровня, который должен обрабатывать много потоков трафика, а затем вам нужен класс, который является потокобезопасным. Моим любимым примером является JavaConcurrentSkipListMap, который выполняет ту же работу, что и TreeMap. Я использую TreeMap 100 раз для каждого использования JCSLM, но когда мне это нужно, это спасатель. (Без этого я бы застрял с Java 1.4.)

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

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

Тем не менее, мне очень повезло, в редких случаях, просто синхронизировать каждый метод в классе. И я могу (намеренно) заблокировать все это просто путем синхронизации с классом. Это просто и безопасно, и не вредит, если класс легко используется. Но я иногда ошибаюсь в отношении использования класса и должен отказаться от него, когда моя программа зависает, используя только 2% процессора, несмотря на 4 гиперпотоковых ядра.

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


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

Заблокируйте метод. Помните, что вам также нужно синхронизировать ваш метод get, или он может отражать несогласованное состояние объекта, потому что, если он не synchronized, он не будет проверять блокировку и может завершиться параллельно, в то время как блокировка объекта удерживается в другом потоке,


Если вы перейдете на синхронизированный блок, он заблокирует определенный объект. Если вы пойдете на синхронизированный метод, он заблокирует все объекты. См. Ниже ссылку, возможно, вы получите ответ.

Есть ли преимущество использования Синхронизированного метода вместо Синхронизированного блока?


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

licensed under cc by-sa 3.0 with attribution.