Ответ
Ключевое слово synchronized в Java используется для обеспечения взаимного исключения (mutual exclusion) и синхронизации потоков при доступе к общим ресурсам, предотвращая состояние гонки (race condition).
Оно может применяться двумя способами:
-
Синхронизированный блок кода:
public void increment() { synchronized (this) { // Монитором служит объект 'this' count++; // Критическая секция } }- В скобках указывается объект-монитор, блокировка которого будет захвачена перед выполнением кода в блоке.
-
Синхронизированный метод:
public synchronized void decrement() { count--; // Весь метод является критической секцией }- Для нестатического метода монитором служит объект
this(экземпляр класса). - Для статического метода монитором служит объект класса (
Classobject).
- Для нестатического метода монитором служит объект
Как это работает:
- Чтобы войти в синхронизированный блок или метод, поток должен получить блокировку (lock) на указанном объекте-мониторе.
- Если блокировка уже удерживается другим потоком, текущий поток переходит в состояние BLOCKED и ждет ее освобождения.
- Когда поток выходит из синхронизированной области (даже из-за исключения), блокировка автоматически освобождается.
Важные замечания:
synchronizedгарантирует видимость изменений (реализует happens-before), поэтому изменения, сделанные в синхронизированном блоке, становятся видимыми всем последующим потокам, которые захватят ту же блокировку.- Это реентерабельная (reentrant) блокировка: поток, уже владеющий блокировкой, может снова захватить ее (например, при рекурсивном вызове синхронизированного метода).
- Недостатки: Может приводить к взаимным блокировкам (deadlock) и снижению производительности из-за сериализации выполнения потоков. Для более сложных сценариев часто предпочтительнее использовать примитивы из пакета
java.util.concurrent.locks.
Ответ 18+ 🔞
А, синхронизация в Java! Ну, это как раз та тема, где можно так накосячить, что потом будешь неделю искать, почему у тебя данные, как говно в проруби, то всплывают, то тонут.
Вот смотри, есть у тебя общая переменная, count там какой-нибудь. И бегут к ней десять потоков, как голодные псы на миску. Все хотят её увеличить. Без контроля — это будет пиздец, прости мой французский. Один поток прочитал, что count = 5, второй тоже успел прочитать 5, пока первый ещё не записал. Оба прибавили единичку и записали обратно 6. А должно было стать 7! Вот это и есть состояние гонки, или race condition, когда результат зависит от того, кто какого хуя первым добежал.
Чтобы такого не было, придумали ключевое слово synchronized. Это такой здоровенный мужик с дубиной у входа в критическую секцию. Хочешь зайти — получи пропуск (блокировку). Нет пропуска — стой, блядь, и жди, пока предыдущий не выйдет.
Применять его можно двумя основными способами, как дубинкой по лбу:
-
Синхронизированный блок. Ты сам указываешь, на каком объекте (мониторе) будешь крутить замок.
public void increment() { synchronized (this) { // Замок крутится на объекте 'this'. Можно на любом другом, хоть на отдельном пустом Object lock = new Object(); count++; // Вот эта святая святых — критическая секция } }Удобно, когда синхронизировать нужно не весь метод, а только его кусок. Меньше тормозить будет.
-
Синхронизированный метод. Проще, но грубее.
public synchronized void decrement() { count--; // Весь метод теперь под замком }Тут есть нюанс, ёпта! Если метод нестатический, то замок крутится на объекте
this. А если статический — то на самом классе (SomeClass.class). Перепутать — и ты уже синхронизируешься на разных вещах, а состояние гонки остаётся, как заноза в жопе.
Как это работает, если по-простому: Поток подходит, видит — замок свободен. Хватает его и заходит работать. Остальные потоки упираются лбом в закрытую дверь и переходят в состояние BLOCKED (заблокирован), тихо материясь в очереди. Как первый выйдет (даже если его выкинет исключением), он замок бросит. И тогда его подхватит самый шустрый из ожидающих.
Что ещё важно, чтобы не обосраться:
- Видимость изменений.
synchronized— это не только про очередь. Он ещё гарантирует, что если один поток в синхронизированном блоке поменял переменную, то следующий поток, зашедший под тот же замок, увидит это изменение. Без этого кэши процессоров могли бы показывать каждому потоку свою старую хуйню. - Реентерабельность. Если поток уже владеет замком, он может зайти ещё в один синхронизированный блок, который требует тот же замок. Это чтобы ты сам себя не заблокировал, например, вызвав один synchronized-метод из другого synchronized-метода того же объекта.
- Недостатки. А они, блядь, есть! Во-первых, взаимная блокировка (deadlock). Два потока ждут друг от друга замки и стоят вечно, как два упрямых осла. Во-вторых, производительность. Если синхронизировать всё подряд, то потоки будут больше стоять в очереди, чем работать. Для сложных сценариев уже давно придумали более хитрые штуки из
java.util.concurrent.locks— там и таймауты есть, и раздельные lock/unlock. Ноsynchronized— это классика, основа, которую надо понимать, как таблицу умножения.