Ответ
Ключевое слово synchronized в Java обеспечивает взаимное исключение (mutual exclusion) и видимость изменений между потоками для синхронизированного кода. Оно предотвращает состояние гонки (race condition).
Принцип работы: Каждый объект в Java имеет связанную с ним внутреннюю блокировку (intrinsic lock или monitor lock). Когда поток входит в synchronized блок или метод, он захватывает эту блокировку. Другие потоки, пытающиеся войти в любой synchronized блок, защищенный той же самой блокировкой, будут заблокированы до ее освобождения.
Два способа использования:
-
Синхронизированный метод: Блокировка захватывается на объекте
this(для нестатических методов) или на объекте классаClass(для статических методов).public synchronized void increment() { counter++; // Потокобезопасно } -
Синхронизированный блок: Позволяет указать объект-блокировку явно, что дает большую гибкость и может уменьшить область блокировки.
private final Object lock = new Object(); private int counter; public void increment() { // Только этот блок кода синхронизирован, а не весь метод synchronized(lock) { counter++; } }
Важные аспекты (happens-before):
- Все изменения, сделанные в
synchronizedблоке, гарантированно видны любому другому потоку, который впоследствии войдет в блок, синхронизированный по той же самой блокировке. - Это решает проблемы с кешами процессоров и переупорядочиванием инструкций.
Недостатки и альтернативы:
- Deadlock: Риск взаимной блокировки потоков.
- Производительность: Захват и освобождение блокировок имеют накладные расходы.
- Альтернативы из
java.util.concurrent: Для более сложных сценариев используйтеReentrantLock,Semaphore, атомарные классы (AtomicInteger) или concurrent-коллекции (ConcurrentHashMap).
Ответ 18+ 🔞
А, ну это же про синхронизацию в Java! Ну что, погнали разбираться, как эти потоки друг другу мозги не выносят, а то ведь реально доходит до рукопашной, если не контролировать.
Вот смотри, есть у тебя объект. У каждого объекта, как у нормального пацана во дворе, есть своя внутренняя блокировка (intrinsic lock), типа его личная дубинка. И когда поток заходит в synchronized метод или блок, он эту дубинку хватает и кричит: «Моё!». Все остальные потоки, которые тоже хотят зайти в синхронизированные места, защищённые этой же самой дубинкой, стоят и ждут, как лохи, пока первый не набегается и не положит её на место.
Как этим пользоваться? Да по-разному, блядь!
-
Синхронизированный метод. Самый простой способ, для ленивых. Весь метод обносится колючкой. Для обычного метода дубинка — это сам объект (
this), а для статического — вообще класс целиком (Classобъект). Грубо, но работает.public synchronized void increment() { counter++; // Теперь тут можно спокойно работать, никто не влезет } -
Синхронизированный блок. А это уже для тех, кто похитрее. Тут ты сам указываешь, какую конкретно дубинку хватать. Можно взять не всего себя (
this), а какую-то отдельную палку. Это часто эффективнее, потому что блокируешь не весь дом, а только один сортир.private final Object lock = new Object(); // Вот она, наша отдельная дубинка private int counter; public void increment() { // Вся остальная фигня в методе может работать без блокировки // А вот этот кусочек — только с нашей дубинкой в руках synchronized(lock) { counter++; } }
А теперь самое важное, про что все забывают — happens-before, ёпта! Когда поток отпускает блокировку, он как бы кричит всем остальным: «Смотрите сюда, мудаки! Все изменения, которые я тут натворил в синхронизированном блоке, теперь официально вступают в силу!». И любой другой поток, который потом схватит эту же самую блокировку, гарантированно увидит все эти изменения. Никаких там кешей процессорных и перестановок команд — всё чётко, как по нотам. Это решает кучу проблем с видимостью, которые возникают просто на ровном месте.
Но не всё так гладко, конечно:
- Deadlock (Взаимная блокировка). Это когда два потока схватили по дубинке и ждут, пока другой отпустит свою, чтобы взять её. И стоят так до скончания времён. Классика жанра, пиздец.
- Производительность. Хватать и отпускать дубинки — это не бесплатно. Если делать это каждую миллисекунду, можно всю скорость угробить.
- Есть же альтернативы, блядь! Не надо всё на
synchronizedвешать. В арсеналеjava.util.concurrentцелая куча инструментов для взрослых:ReentrantLock(блокировка с доп. фичами),Semaphore(пропуск в ночной клуб), атомарные штуки типаAtomicInteger(меняй значение без всяких блокировок) и потоко-безопасные коллекции вродеConcurrentHashMap. Ими и пользуйся, когда обычной дубинки уже мало.