ПрограммированиеBackend разработчик

Опишите, как работает synchronized-блок в Java. Каковы его особенности, как выбрать объект для синхронизации и как неправильный выбор может привести к ошибкам?

Проходите собеседования с ИИ помощником Hintsage

Ответ.

synchronized — ключевое слово, позволяющее выполнять потокобезопасный доступ к критическим участкам кода. Оно может использоваться либо для метода (например, public synchronized void foo()), либо для блока кода (synchronized(obj) { ... }). Когда поток входит в synchronized-блок, он получает монитор (lock) объекта. Пока монитор занят, другие потоки не могут войти в другой synchronized-блок, использующий тот же объект в качестве монитора.

Особенности:

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

Пример выбор объекта синхронизации

public class Counter { private int count; private final Object lock = new Object(); // приватный объект public void increment() { synchronized(lock) { count++; } } }

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

Вопрос с подвохом.

Вопрос: Что произойдет, если синхронизироваться на объекте типа String, содержащем фиксированное значение?

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

Пример (нельзя так делать):

synchronized("LOCK") { ... }

Примеры реальных ошибок из-за незнания тонкостей темы.


История

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


История

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


История

Для синхронизации был выбран каждый раз новый объект: synchronized(new Object()) { ... }. В результате синхронизация не работала вообще, and к данным имели одновременный доступ разные потоки. Это было обнаружено только на нагрузочном тестировании.