Избегайте синхронизации (это) в Java?
Всякий раз, когда возникает вопрос о SO о синхронизации Java, некоторые люди очень хотят указать, что synchronized(this)
следует избегать. Вместо этого они утверждают, что предпочтительнее блокировка частной ссылки.
Некоторые из приведенных причин:
Другие люди, включая меня, утверждают, что synchronized(this)
- это идиома, которая много используется (также в Java-библиотеках), является безопасной и понятной. Его не следует избегать, потому что у вас есть ошибка, и вы не знаете, что происходит в вашей многопоточной программе. Другими словами: если это применимо, используйте его.
Мне интересно увидеть некоторые реальные примеры (без foobar stuff), где избегать блокировки на this
предпочтительнее, когда synchronized(this)
также выполнит задание.
Следовательно: следует ли всегда избегать synchronized(this)
и заменить его на блокировку частной ссылки?
Дополнительная информация (обновляется с учетом ответов):
- мы говорим о синхронизации экземпляров
- рассматриваются как неявные (
synchronized
методы), так и явный вид synchronized(this)
- если вы цитируете Блоха или другие органы по этому вопросу, не оставляйте без внимания те части, которые вам не нравятся (например, "Эффективная Java", пункт "Безопасность потока": обычно это блокировка самого экземпляра, но есть исключения.)
- Если вам нужна гранулярность в вашей блокировке, кроме
synchronized(this)
, то synchronized(this)
не применяется, так что не проблема
Ответы
Ответ 1
Я буду охватывать каждую точку отдельно.
-
Некоторый злой код может украсть ваш замок (очень популярный этот, также имеет "случайный" вариант)
Меня больше беспокоит случайность. Это то, что это использование this
является частью открытого интерфейса вашего класса и должно быть документировано. Иногда требуется возможность использования другого кода для блокировки. Это относится к вещам типа Collections.synchronizedMap
(см. Javadoc).
-
Все синхронизированные методы в одном классе используют то же самое блокировка, что снижает пропускную способность
Это слишком упрощенное мышление; просто избавиться от synchronized(this)
не решит проблему. Правильная синхронизация пропускной способности займет больше времени.
-
Вы (излишне) подвергаете слишком много информации
Это вариант # 1. Использование synchronized(this)
является частью вашего интерфейса. Если вы не хотите/нуждаетесь в этом, не делайте этого.
Ответ 2
Хорошо, во-первых, следует отметить, что:
public void blah() {
synchronized (this) {
// do stuff
}
}
семантически эквивалентно:
public synchronized void blah() {
// do stuff
}
что является одной из причин не использовать synchronized(this)
. Вы можете утверждать, что вы можете делать вещи вокруг блока synchronized(this)
. Обычная причина заключается в том, чтобы попытаться избежать необходимости выполнять синхронизированную проверку вообще, что приводит к возникновению всех проблем concurrency, в частности проблема double check-locking, который просто показывает, насколько сложно сделать относительно простую проверку потокобезопасности.
Частный замок - это защитный механизм, который никогда не является плохим.
Кроме того, как вы указали, частные блокировки могут управлять детализацией. Один набор операций над объектом может быть полностью не связан с другим, но synchronized(this)
будет взаимно исключать доступ ко всем из них.
synchronized(this)
просто ничего не дает.
Ответ 3
Пока вы используете синхронизированный (это), вы используете экземпляр класса в качестве самой блокировки. Это означает, что пока блокировка получена потоком 1, поток 2 должен ждать.
Предположим, следующий код:
public void method1() {
// do something ...
synchronized(this) {
a ++;
}
// ................
}
public void method2() {
// do something ...
synchronized(this) {
b ++;
}
// ................
}
Метод 1, модифицирующий переменную a, и метод 2, модифицирующий переменную b, следует избегать одновременного изменения одной и той же переменной двумя потоками, и это так. НО, хотя thread1 модифицирует a и thread2 модифицирует b, это может быть выполнено без каких-либо условий гонки.
К сожалению, приведенный выше код не позволит этого, так как мы используем ту же ссылку для блокировки; Это означает, что потоки, даже если они не находятся в состоянии гонки, должны ждать и, очевидно, код жертвует параллелизмом программы.
Решение состоит в том, чтобы использовать 2 разных блокировки для двух разных переменных:
public class Test {
private Object lockA = new Object();
private Object lockB = new Object();
public void method1() {
// do something ...
synchronized(lockA) {
a ++;
}
// ................
}
public void method2() {
// do something ...
synchronized(lockB) {
b ++;
}
// ................
}
}
В приведенном выше примере используются более мелкозернистые блокировки (2 блокировки вместо одной (lockA и lockB для переменных a и b соответственно) и, как результат, обеспечивает лучший параллелизм, с другой стороны, он стал более сложным, чем в первом примере...
Ответ 4
В то время как я соглашаюсь не придерживаться вслепую догматических правил, делает ли сценарий "блокировки кражи" настолько эксцентричным для вас? Нить действительно могла бы получить блокировку вашего объекта "извне" (synchronized(theObject) {...}
), блокируя другие потоки, ожидающие синхронных методов экземпляра.
Если вы не верите в вредоносный код, считайте, что этот код может поступать от третьих лиц (например, если вы разрабатываете какой-то сервер приложений).
"Случайная" версия кажется менее вероятной, но, как говорится, "сделайте что-то идиот-доказательство, и кто-то придумает лучшего идиота".
Таким образом, я согласен с тем, что он - зависит от того, что класс - это школа мысли.
Изменить следующие первые 3 комментария:
Я никогда не сталкивался с проблемой блокировки блокировки, но вот мнимый сценарий:
Скажем, ваша система является контейнером сервлета, и объект, который мы рассматриваем, является реализацией ServletContext
. Его метод getAttribute
должен быть потокобезопасным, поскольку атрибуты контекста являются общими данными; поэтому вы объявляете его как synchronized
. Также представьте, что вы предоставляете общедоступную услугу хостинга на основе вашей реализации контейнера.
Я ваш клиент и размещаю свой "хороший" сервлет на вашем сайте. Бывает, что мой код содержит вызов getAttribute
.
Хакер, замаскированный под другой клиент, разворачивает свой вредоносный сервлет на вашем сайте. Он содержит следующий код в методе init
:
synchronized (this.getServletConfig().getServletContext()) {
while (true) {}
}
Предполагая, что мы разделяем один и тот же контекст сервлета (разрешенный спецификацией, пока два сервлета находятся на одном и том же виртуальном хосте), мой вызов getAttribute
заблокирован навсегда. Хакер добился DoS на моем сервлете.
Эта атака невозможна, если getAttribute
синхронизируется в частной блокировке, потому что сторонний код не может получить эту блокировку.
Я признаю, что этот пример изобретателен и имеет упрощенное представление о том, как работает контейнер сервлета, но IMHO он доказывает.
Итак, я бы сделал свой выбор дизайна на основе соображений безопасности: будет ли я иметь полный контроль над кодом, который имеет доступ к экземплярам? Что было бы следствием того, что поток, содержащий блокировку экземпляра неограниченно?
Ответ 5
По-видимому, в лагерях С# и Java существует другое согласие.. Большинство кода Java, которые я видел, использует:
// apply mutex to this instance
synchronized(this) {
// do work here
}
тогда как большинство С# -кодов выбирают более безопасное:
// instance level lock object
private readonly object _syncObj = new object();
...
// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
// do work here
}
Идиома С#, безусловно, безопаснее. Как упоминалось ранее, вредоносный/случайный доступ к блокировке не может быть сделан извне экземпляра. Java-код тоже имеет этот риск, , но кажется, что сообщество Java со временем тяготело к немного менее безопасной, но немного более короткой версии.
Это не значит, что вы можете прогнать Java, просто отражая мой опыт работы на обоих языках.
Ответ 6
Это зависит от ситуации.
Если существует только один объект совместного использования или более одного.
Смотрите полный рабочий пример здесь
Небольшое вступление.
Потоки и общие объекты
Для нескольких потоков возможно получение доступа к одному и тому же объекту, например, для нескольких соединений ThreadThreads, совместно использующих одно сообщениеQueue. Поскольку потоки выполняются одновременно, может возникнуть вероятность переопределения одних данных другими, что может привести к путанице.
Таким образом, нам нужен какой-то способ обеспечить доступ к разделяемому объекту только одному потоку за раз. (Параллелизм).
Синхронизированный блок
Блок synchronized() - это способ обеспечить одновременный доступ к разделяемому объекту.
Сначала небольшая аналогия
Предположим, что в умывальной комнате находятся два человека P1, P2 (нити), Раковина (разделяемый объект) и дверь (замок).
Теперь мы хотим, чтобы один человек пользовался раковиной одновременно.
Подход заключается в том, чтобы запереть дверь с помощью P1, когда дверь заперта. P2 ждет, пока p1 не завершит свою работу.
P1 отпирает дверь
тогда только p1 может использовать умывальник.
синтаксис.
synchronized(this)
{
SHARED_ENTITY.....
}
"this" обеспечивает внутреннюю блокировку, связанную с классом (Java-разработчик разработал класс Object таким образом, чтобы каждый объект мог работать как монитор). Вышеупомянутый подход работает нормально, когда есть только одна общая сущность и несколько потоков (1: N).
N разделяемых объектов-M потоков
Теперь представьте себе ситуацию, когда внутри умывальника есть две раковины и только одна дверь. Если мы используем предыдущий подход, только p1 может использовать одну раковину за раз, в то время как p2 будет ждать снаружи. Это пустая трата ресурсов, так как никто не использует B2 (умывальник).
Более разумным подходом было бы создать меньшую комнату внутри уборной и предоставить им одну дверь на раковину. Таким образом, P1 может получить доступ к B1, а P2 может получить доступ к B2 и наоборот.
washbasin1;
washbasin2;
Object lock1=new Object();
Object lock2=new Object();
synchronized(lock1)
{
washbasin1;
}
synchronized(lock2)
{
washbasin2;
}
Подробнее о темах → здесь
Ответ 7
Пакет java.util.concurrent
значительно уменьшил сложность кода, защищенного потоком. У меня есть только анекдотические доказательства, но большинство работ, которые я видел с помощью synchronized(x)
, как представляется, повторно реализуют Lock, Semaphore или Latch, но используют мониторы более низкого уровня.
Учитывая это, синхронизация с использованием любого из этих механизмов аналогична синхронизации на внутреннем объекте, а не утечке блокировки. Это полезно в том, что у вас есть абсолютная уверенность в том, что вы контролируете запись на монитор двумя или более потоками.
Ответ 8
- Сделайте ваши данные неизменными, если это возможно (
final
variables)
- Если вы не можете избежать мутации общих данных в нескольких потоках, используйте конструкторы программирования высокого уровня (например, гранулированный
Lock
API]
Блокировка обеспечивает эксклюзивный доступ к общему ресурсу: только один поток за один раз может получить блокировку, и все доступ к совместно используемому ресурсу требует, чтобы блокировка была получена первой.
Пример кода для использования ReentrantLock
, который реализует интерфейс Lock
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
Преимущества блокировки по синхронному (это)
-
Использование синхронизированных методов или операторов заставляет все блокирование и освобождение блокироваться блочно-структурированным способом.
-
Реализации блокировки предоставляют дополнительную функциональность по сравнению с использованием синхронизированных методов и инструкций, предоставляя
- Неблокирующая попытка получить блокировку (
tryLock()
)
- Попытка получить блокировку, которая может быть прервана (
lockInterruptibly()
)
- Попытка получить блокировку, которая может быть таймаутом (
tryLock(long, TimeUnit)
).
-
Класс блокировки также может обеспечивать поведение и семантику, которые сильно отличаются от поведения неявной блокировки монитора, например
- гарантированный порядок
- использование без повторного участия
- Определение тупика
Посмотрите на этот вопрос SE относительно различного типа Locks
:
Синхронизация и блокировка
Вы можете добиться безопасности потоков, используя расширенный API concurrency вместо синхронизированных блоков. Эта документация страница предоставляет хорошие конструкторы программирования для обеспечения безопасности потоков.
Блокировать объекты поддерживают блокировки идиом, которые упрощают многие параллельные приложения.
Исполнители определяют API высокого уровня для запуска и управления потоками. Реализации Executor, предоставленные java.util.concurrent, обеспечивают управление пулами потоков, подходящее для крупномасштабных приложений.
Concurrent Collections упрощает управление большими наборами данных и может значительно уменьшить необходимость синхронизации.
Атомные переменные имеют функции, которые минимизируют синхронизацию и помогают избежать ошибок согласованности памяти.
ThreadLocalRandom (в JDK 7) обеспечивает эффективное генерирование псевдослучайных чисел из нескольких потоков.
Обратитесь к java.util.concurrent и java.util.concurrent.atomic для других программных конструкций.
Ответ 9
Если вы решили, что:
- то, что вам нужно сделать, это заблокировать
текущий объект; и
- вы хотите
заблокируйте его с зернистостью меньше, чем
целый метод;
то я не вижу табу над synchronizezd (this).
Некоторые люди намеренно используют синхронизированные (это) (вместо того, чтобы маркировать синхронизированный метод) внутри всего содержимого метода, потому что они считают его "более понятным для читателя", на котором объект фактически синхронизируется. До тех пор, пока люди делают осознанный выбор (например, понимают, что, фактически, они вставляют дополнительные байт-коды в метод, и это может иметь эффект детонации для потенциальных оптимизаций), я особенно не вижу проблемы с этим, Вы должны всегда документировать параллельное поведение вашей программы, поэтому я не вижу, чтобы аргумент "synchronized" публиковал поведение "как убедительный.
Что касается вопроса о том, какую блокировку объекта вы должны использовать, я думаю, что нет ничего плохого в синхронизации с текущим объектом , если бы это ожидалось логикой того, что вы делаете, и как обычно будет выглядеть ваш класс используется. Например, с помощью коллекции объект, который вы логически ожидаете заблокировать, обычно является самой коллекцией.
Ответ 10
Я думаю, что есть хорошее объяснение того, почему каждый из них - жизненно важные методы под вашим поясом в книге под названием Java Concurrency In Practice Брайана Гетца. Он делает одно очко очень ясно - вы должны использовать один и тот же замок "ВЕЗДЕ", чтобы защитить состояние вашего объекта. Синхронный метод и синхронизация на объекте часто идут рука об руку. Например. Вектор синхронизирует все его методы. Если у вас есть дескриптор векторного объекта и вы собираетесь делать "put if absent" , тогда просто синхронизация Vector с его собственными индивидуальными методами не защитит вас от коррупции государства. Вам нужно синхронизировать с помощью synchronized (vectorHandle). Это приведет к тому, что SAME блокируется каждым потоком, который имеет дескриптор вектора и будет защищать общее состояние вектора. Это называется блокировкой на стороне клиента. Мы действительно знаем, что вектор синхронизирован (это)/синхронизирует все его методы и, следовательно, синхронизирует с объектом vectorHandle, приведет к правильной синхронизации состояния векторных объектов. Его глупо полагать, что вы потокобезопасны только потому, что используете поточную безопасную коллекцию. Именно по этой причине ConcurrentHashMap явно вводит метод putIfAbsent - чтобы сделать такие операции атомарными.
В заключение
- Синхронизация на уровне метода позволяет блокировать клиентскую сторону.
- Если у вас есть частный объект блокировки - это делает невозможным блокировку на стороне клиента. Это прекрасно, если вы знаете, что у вашего класса нет функциональности типа "put if absent" .
- Если вы создаете библиотеку, то синхронизация по этому или синхронизация метода часто бывает более мудрой. Потому что вы редко можете решить, как будет использоваться ваш класс.
- Если бы вектор использовал частный объект блокировки - было бы невозможно получить "put if absent" right. Клиентский код никогда не получит дескриптор закрытого замка, нарушив основное правило использования ТОЧНОГО ЗАМКИ, чтобы защитить его состояние.
- Синхронизация по этому или синхронизированным методам имеет проблему, как указывали другие, - кто-то может получить блокировку и никогда не выпускать ее. Все остальные потоки будут ждать выхода блокировки.
- Знайте, что вы делаете, и принимайте то, что правильно.
- Кто-то утверждал, что наличие частного объекта блокировки дает вам лучшую детализацию - например, если две операции не связаны друг с другом - они могут быть защищены различными замками, что обеспечивает лучшую пропускную способность. Но я думаю, что это запах дизайна, а не запах кода - если две операции полностью не связаны между собой, почему они являются частью класса SAME? Почему классный клуб не связан с функциональностью вообще? Может быть, класс полезности? Хмммм - кто-то из них использует строковые манипуляции и форматирование даты в календаре через один и тот же экземпляр?... не имеет для меня никакого смысла!
Ответ 11
Нет, не всегда. Тем не менее, я стараюсь избегать этого, когда есть несколько проблем для конкретного объекта, которые должны быть только потокобезопасными по отношению к себе. Например, у вас может быть измененный объект данных с полями "label" и "parent"; они должны быть потокобезопасными, но изменение одного не должно блокировать другое от написания/чтения. (На практике я бы избегал этого, объявляя поле volatile и/или используя java.util.concurrent AtomicFoo обертки).
Синхронизация вообще немного неуклюжая, поскольку она шлепает большой замок, а не думает точно, как потоки могут быть разрешены для работы друг с другом. Использование synchronized(this)
еще более неуклюже и антисоциально, так как он говорит: "Никто не может что-либо изменить в этом классе, пока я держу замок". Как часто вам действительно нужно это делать?
Я бы предпочел бы более гранулированные замки; даже если вы хотите, чтобы все перестало меняться (возможно, вы сериализуете объект), вы можете просто приобрести все блокировки для достижения того же, а также более явным образом. Когда вы используете synchronized(this)
, неясно, почему вы синхронизируете или какие побочные эффекты могут быть. Если вы используете synchronized(labelMonitor)
или даже лучше labelLock.getWriteLock().lock()
, он очищает то, что вы делаете, и каковы последствия вашего критического раздела.
Ответ 12
Короткий ответ. Вы должны понимать разницу и делать выбор в зависимости от кода.
Длинный ответ. В общем, я бы предпочел избежать синхронизации (для этого), чтобы уменьшить конфликт, но частные блокировки добавляют сложность, о которой вы должны знать. Поэтому используйте правильную синхронизацию для правильной работы. Если вы не так много опытов с многопоточным программированием, я предпочел бы придерживаться блокировки экземпляра и читать эту тему. (Тем не менее: просто использование synchronize (это) не делает ваш класс полностью потокобезопасным.) Это непростая тема, но как только вы привыкнете к этому, ответ на вопрос, использовать ли синхронизацию (это) или нет,.
Ответ 13
Блокировка используется для видимости или для защиты некоторых данных от одновременной модификации, которая может привести к расе.
Если вам нужно просто сделать операции примитивного типа атомарными, доступны такие параметры, как AtomicInteger
и т.д.
Но предположим, что у вас есть два целых числа, которые связаны друг с другом, такие как координаты x
и y
, которые связаны друг с другом и должны быть изменены атомарным образом. Затем вы будете защищать их с помощью одной и той же блокировки.
Блокировка должна защищать только состояние, связанное друг с другом. Не меньше и не больше. Если вы используете synchronized(this)
в каждом методе, то даже если состояние класса не связано, все потоки будут сталкиваться с конфликтом даже при обновлении несвязанного состояния.
class Point{
private int x;
private int y;
public Point(int x, int y){
this.x = x;
this.y = y;
}
//mutating methods should be guarded by same lock
public synchronized void changeCoordinates(int x, int y){
this.x = x;
this.y = y;
}
}
В приведенном выше примере у меня есть только один метод, который мутирует как x
, так и y
, а не два разных метода в качестве x
и y
связаны, и если бы я дал два разных метода для мутирования x
и y
отдельно, тогда он не был бы потокобезопасным.
Этот пример предназначен только для демонстрации и не обязательно того, как он должен быть реализован. Лучший способ сделать это - сделать его IMMUTABLE.
Теперь, в противоположность примеру Point
, существует пример TwoCounters
, уже предоставленный @Andreas, где состояние, которое защищено двумя разными блокировками как состояние, не связано друг с другом.
Процесс использования разных блокировок для защиты несвязанных состояний называется Lock Striping или Lock Splitting
Ответ 14
Причиной не синхронизировать это является то, что иногда вам нужно больше одного замка (вторая блокировка часто удаляется после некоторого дополнительного мышления, но вам все еще нужна она в промежуточном состоянии). Если вы заблокируете это, вы всегда должны помнить, какой из двух замков это; если вы заблокируете частный объект, имя переменной сообщит вам об этом.
С точки зрения читателя, если вы видите блокировку на этом, вам всегда нужно ответить на два вопроса:
- Какой доступ защищен этим?
- достаточно один замок, разве кто-нибудь не вводит ошибку?
Пример:
class BadObject {
private Something mStuff;
synchronized setStuff(Something stuff) {
mStuff = stuff;
}
synchronized getStuff(Something stuff) {
return mStuff;
}
private MyListener myListener = new MyListener() {
public void onMyEvent(...) {
setStuff(...);
}
}
synchronized void longOperation(MyListener l) {
...
l.onMyEvent(...);
...
}
}
Если два потока начинаются с longOperation()
на двух разных экземплярах BadObject
, они приобретают
их замки; когда пришло время для вызова l.onMyEvent(...)
, у нас есть тупик, потому что ни один из потоков не может получить другую блокировку объекта.
В этом примере мы можем устранить тупик, используя две блокировки: одну для коротких операций и одну для длинных.
Ответ 15
Как уже говорилось, синхронизированный блок может использовать пользовательскую переменную в качестве объекта блокировки, когда синхронизированная функция использует только "this". И, конечно, вы можете манипулировать областями вашей функции, которые должны быть синхронизированы и т.д.
Но все говорят, что нет никакой разницы между синхронизированной функцией и блоком, которая охватывает всю функцию, используя "this" как объект блокировки. Это неверно, разница в байтовом коде, который будет генерироваться в обеих ситуациях. В случае использования синхронизированного блока должна быть назначена локальная переменная, которая содержит ссылку на "this". И в результате у нас будет немного больший размер функции (не уместно, если у вас всего несколько функций).
Более подробное объяснение разницы вы можете найти здесь:
http://www.artima.com/insidejvm/ed2/threadsynchP.html
Также использование синхронизированного блока не является хорошим из-за следующей точки зрения:
Синхронизированное ключевое слово очень ограничено в одной области: при выходе из синхронизированного блока все потоки, которые ждут этой блокировки, должны быть разблокированы, но только один из этих потоков получает блокировку; все остальные видят, что замок сделан и возвращается в заблокированное состояние. Это не просто много запущенных циклов обработки: часто контекстный переключатель для разблокирования потока также включает в себя пейджинговую память с диска, и это очень, очень дорого.
Для более подробной информации в этой области я бы рекомендовал вам прочитать эту статью:
http://java.dzone.com/articles/synchronized-considered
Ответ 16
Это на самом деле просто дополняет другие ответы, но если ваше основное возражение против использования частных объектов для блокировки состоит в том, что он загромождает ваш класс полями, не связанными с бизнес-логикой, тогда Project Lombok имеет @Synchronized
для генерации шаблона при компиляции. время:
@Synchronized
public int foo() {
return 0;
}
компилируется в
private final Object $lock = new Object[0];
public int foo() {
synchronized($lock) {
return 0;
}
}
Ответ 17
Это зависит от задачи, которую вы хотите сделать, но я бы не использовал ее. Кроме того, проверьте, не удалось ли выполнить синхронизацию (это), если вы хотите сопровождать поток, в первую очередь? Также есть несколько хороших блокировок в API, которые могут вам помочь:)
Ответ 18
Хороший пример использования синхронизации (this).
// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
// some code here...
Set ls;
synchronized(this) {
ls = listeners.clone();
}
for (IListener l : ls) { l.processEvent(event); }
// some code here...
}
Как вы можете видеть здесь, мы используем для этого синхронизацию для простого взаимодействия длинного (возможно, бесконечного цикла метода run) с некоторыми там синхронизируемыми методами.
Конечно, его можно очень легко переписать с помощью синхронизации в частном поле. Но иногда, когда у нас уже есть дизайн с синхронизированными методами (т.е. Унаследованный класс, из которого мы получаем, синхронизированный (это) может быть единственным решением).
Ответ 19
Я только хочу упомянуть возможное решение для уникальных частных ссылок в атомарных частях кода без зависимостей. Вы можете использовать статический Hashmap с блокировками и простой статический метод с именем atomic(), который автоматически создает необходимые ссылки, используя информацию стека (полное имя класса и номер строки). Затем вы можете использовать этот метод для синхронизации операторов без записи нового объекта блокировки.
// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point
StackTraceElement exepoint = stack[2];
// creates unique key from class name and line number using execution point
String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber());
Object lock = locks.get(key); // use old or create new lock
if (lock == null) {
lock = new Object();
locks.put(key, lock);
}
return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
// start commands
synchronized (atomic()) {
// atomic commands 1
...
}
// other command
}
// Synchronized code
void dosomething2() {
// start commands
synchronized (atomic()) {
// atomic commands 2
...
}
// other command
}
Ответ 20
Избегайте использования synchronized(this)
в качестве механизма блокировки: это блокирует весь экземпляр класса и может вызвать взаимные блокировки. В таких случаях реорганизуйте код, чтобы заблокировать только определенный метод или переменную, чтобы весь класс не блокировался. Synchronised
может использоваться внутри уровня метода.
Вместо использования synchronized(this)
, приведенный ниже код показывает, как можно просто заблокировать метод.
public void foo() {
if(operation = null) {
synchronized(foo) {
if (operation == null) {
// enter your code that this method has to handle...
}
}
}
}
Ответ 21
Мои два цента в 2019 году, хотя этот вопрос уже мог быть решен.
Блокировка "this" - это неплохо, если вы знаете, что делаете, но за сценой стоит блокировка "this" (что, к сожалению, позволяет синхронизированное ключевое слово в определении метода).
Если вы действительно хотите, чтобы пользователи вашего класса могли "украсть" вашу блокировку (то есть не допустить, чтобы другие потоки имели с ней дело), вы действительно хотите, чтобы все синхронизированные методы ожидали, пока запущен другой метод синхронизации, и так далее. Он должен быть преднамеренным и хорошо продуманным (и, следовательно, задокументированным, чтобы помочь вашим пользователям понять это).
Чтобы уточнить, наоборот, вы должны знать, что вы "получаете" (или "теряете"), если блокируете недоступную блокировку (никто не может "украсть" вашу блокировку, вы полностью контролируете и так далее)...).
Для меня проблема в том, что ключевое слово synchronized в сигнатуре определения метода позволяет программистам слишком легко не думать о том, что блокировать, о чем очень важно думать, если вы не хотите сталкиваться с проблемами в мульти программа.
Нельзя утверждать, что "как правило" вы не хотите, чтобы пользователи вашего класса могли делать такие вещи, или что "обычно" вы хотите... Это зависит от того, какую функциональность вы кодируете. Вы не можете сделать правило большого пальца, поскольку вы не можете предсказать все варианты использования.
Рассмотрим, например, устройство печати, которое использует внутреннюю блокировку, но затем люди пытаются использовать ее из нескольких потоков, если они не хотят, чтобы их выходные данные чередовались.
Если ваша блокировка доступна вне класса или нет, это ваше решение как программиста, исходя из того, какие функциональные возможности есть в классе. Это часть API. Например, вы не можете перейти от синхронизированного (this) к синхронизированному (provateObjet), не рискуя нарушить изменения в коде, использующем его.
Примечание 1: я знаю, что вы можете добиться того, что синхронизируется (это) "достигает", используя явный объект блокировки и выставляя его, но я думаю, что это не нужно, если ваше поведение хорошо документировано и вы действительно знаете, что означает блокировка "this".
Примечание 2: я не согласен с аргументом, что если какой-то код случайно украл вашу блокировку, это ошибка, и вы должны ее решить. В некотором смысле это тот же аргумент, что и я могу сделать все мои методы публичными, даже если они не предназначены для публичности. Если кто-то "случайно" вызывает мой намеренный закрытый метод, это ошибка. Зачем включать эту аварию в первую очередь !!! Если способность украсть ваш замок - проблема для вашего класса, не позволяйте это. Так просто.
Ответ 22
Я думаю, что точки один (кто-то другой использует вашу блокировку) и два (все методы с использованием той же блокировки бесполезно) могут произойти в любом довольно большом приложении. Особенно, когда между разработчиками нет хорошей связи.
Он не бросается в камень, это в основном проблема хорошей практики и предотвращения ошибок.