Аннотации для работы с синхронизацией
Аннотация @Synchronized — управление синхронизацией
Аннотация @Synchronized позволяет создавать синхронизированные методы, аналогично использованию ключевого слова synchronized в Java. Однако @Synchronized предоставляет больше гибкости, так как позволяет синхронизировать методы на разных объектах.
Пример использования:
Как это работает под капотом?
Lombok генерирует код, который использует блоки synchronized для обеспечения потокобезопасности. Вот что происходит:
Генерация байт-кода:
Если аннотация @Synchronized используется без параметров, Lombok создает скрытое поле $lock и синхронизирует метод на этом поле.
Если указан параметр (например, @Synchronized("lock")), Lombok синхронизирует метод на указанном объекте.
Пример сгенерированного кода:
Создание скрытых полей:
Если объект для синхронизации не указан, Lombok создает скрытое поле $lock типа Object. Это поле используется для синхронизации всех методов, помеченных @Synchronized без параметров.
Нюансы использования
Производительность:
Использование @Synchronized может привести к снижению производительности, так как синхронизация блокирует выполнение кода в других потоках. Это особенно важно в высоконагруженных приложениях.
Взаимодействие с другими аннотациями:
@Synchronized можно комбинировать с другими аннотациями Lombok, такими как @Getter, @Setter, @Data. Однако будьте осторожны при использовании с аннотациями, которые генерируют методы (например, @Builder), так как это может привести к неожиданному поведению.
Ограничение на объекты синхронизации:
Объект, указанный в @Synchronized("lock"), должен быть финальным (final), чтобы избежать изменений во время выполнения. В противном случае синхронизация может работать некорректно.
Проблемы с наследованием:
Если метод, помеченный @Synchronized, переопределяется в подклассе, синхронизация может не работать, так как Lombok генерирует код только для текущего класса.
Альтернативы
Вместо @Synchronized можно использовать более современные механизмы синхронизации, такие как ReentrantLock или StampedLock, которые предоставляют больше гибкости и контроля.
#Java #Training #Spring #Lombok #Synchronized
Аннотация @Synchronized — управление синхронизацией
Аннотация @Synchronized позволяет создавать синхронизированные методы, аналогично использованию ключевого слова synchronized в Java. Однако @Synchronized предоставляет больше гибкости, так как позволяет синхронизировать методы на разных объектах.
Пример использования:
import lombok.Synchronized;
public class Counter {
private int count = 0;
@Synchronized
public void increment() {
count++;
}
@Synchronized("lock")
public void decrement() {
count--;
}
private final Object lock = new Object();
}
Как это работает под капотом?
Lombok генерирует код, который использует блоки synchronized для обеспечения потокобезопасности. Вот что происходит:
Генерация байт-кода:
Если аннотация @Synchronized используется без параметров, Lombok создает скрытое поле $lock и синхронизирует метод на этом поле.
Если указан параметр (например, @Synchronized("lock")), Lombok синхронизирует метод на указанном объекте.
Пример сгенерированного кода:
public class Counter {
private int count = 0;
private final Object $lock = new Object(); // Скрытое поле для синхронизации
private final Object lock = new Object();
public void increment() {
synchronized ($lock) {
count++;
}
}
public void decrement() {
synchronized (lock) {
count--;
}
}
}Создание скрытых полей:
Если объект для синхронизации не указан, Lombok создает скрытое поле $lock типа Object. Это поле используется для синхронизации всех методов, помеченных @Synchronized без параметров.
Нюансы использования
Производительность:
Использование @Synchronized может привести к снижению производительности, так как синхронизация блокирует выполнение кода в других потоках. Это особенно важно в высоконагруженных приложениях.
Взаимодействие с другими аннотациями:
@Synchronized можно комбинировать с другими аннотациями Lombok, такими как @Getter, @Setter, @Data. Однако будьте осторожны при использовании с аннотациями, которые генерируют методы (например, @Builder), так как это может привести к неожиданному поведению.
Ограничение на объекты синхронизации:
Объект, указанный в @Synchronized("lock"), должен быть финальным (final), чтобы избежать изменений во время выполнения. В противном случае синхронизация может работать некорректно.
Проблемы с наследованием:
Если метод, помеченный @Synchronized, переопределяется в подклассе, синхронизация может не работать, так как Lombok генерирует код только для текущего класса.
Альтернативы
Вместо @Synchronized можно использовать более современные механизмы синхронизации, такие как ReentrantLock или StampedLock, которые предоставляют больше гибкости и контроля.
#Java #Training #Spring #Lombok #Synchronized
👍1
Под капотом многопоточной синхронизации в Java: как потоки договариваются через Mark Word
Когда вы пишете synchronized(obj), под капотом происходит целая цепочка событий, которую можно отследить до Mark Word — восьмибайтового служебного поля в каждом Java-объекте. В современных реализациях JVM (таких как HotSpot, OpenJ9, GraalVM) используется динамическая, адаптивная система, которая выбирает наиболее эффективную стратегию блокировки в зависимости от реального поведения потоков.
Каждый объект — это больше, чем кажется
Когда вы создаёте new Object(), JVM выделяет в памяти не только память под данные, но и под служебный заголовок:
Mark Word — это цифровой "паспорт" объекта, который JVM использует для нескольких целей:
Синхронизация (самое важное для нас)
Сборка мусора (отметка и возраст объекта)
Хэш-код (при первом вызове hashCode())
Смещённая блокировка (устаревшая оптимизация)
Представьте Mark Word как восьмизначный шестнадцатеричный номер, где последние 2 цифры — это "статус объекта".
В Mark Word кодируются несколько логических состояний, из которых для синхронизации значимы следующие:
Неблокированный (unlocked) — самый частый. Объект свободен или только что вышел из synchronized-блока.
Лёгкая/тонкая блокировка (thin lock) — один поток вошёл в synchronized без конкуренции.
Тяжёлая/раздутая блокировка (inflated lock) — используется при конкуренции или вызове wait()/notify().
Отмечен для сборки мусора (marked for GC) — временное состояние во время работы сборщика мусора.
Хэш сохранён (hash stored) — после вызова System.identityHashCode().
Смещённая заглушка (biased stub) — историческое примечание: эта оптимизация (1998-2021) "резервировала" объект за потоком, но была полностью удалена из OpenJDK в Java 18, так как в многопоточных приложениях отмена смещения стоила дороже выгоды.
Смещённый заголовок (displaced header) — временное состояние при переходе между другими состояниями.
Важное уточнение: Конкретные битовые паттерны (например, 01 для unlocked) зависят от реализации JVM. HotSpot, OpenJ9 и GraalVM могут использовать разные комбинации битов для одних и тех же концептуальных состояний.
Как JVM выбирает состояние блокировки
Логика выбора стратегии блокировки в псевдокоде (концептуально, детали различаются между JVM):
#Java #middle #monitor #synchronized #mark_word
Когда вы пишете synchronized(obj), под капотом происходит целая цепочка событий, которую можно отследить до Mark Word — восьмибайтового служебного поля в каждом Java-объекте. В современных реализациях JVM (таких как HotSpot, OpenJ9, GraalVM) используется динамическая, адаптивная система, которая выбирает наиболее эффективную стратегию блокировки в зависимости от реального поведения потоков.
Каждый объект — это больше, чем кажется
Когда вы создаёте new Object(), JVM выделяет в памяти не только память под данные, но и под служебный заголовок:
// Визуализация объекта в памяти (64-bit, compressed oops):
Object obj = new Object();
// Адрес в памяти: 0x000000076ab0c410
// Содержимое:
// [0x0000000000000001] ← Mark Word (8 байт)
// [0x00000007c0060c00] ← Указатель на класс (4/8 байт)
// [данные объекта...] ← Поля вашего класса
Mark Word — это цифровой "паспорт" объекта, который JVM использует для нескольких целей:
Синхронизация (самое важное для нас)
Сборка мусора (отметка и возраст объекта)
Хэш-код (при первом вызове hashCode())
Смещённая блокировка (устаревшая оптимизация)
Представьте Mark Word как восьмизначный шестнадцатеричный номер, где последние 2 цифры — это "статус объекта".
В Mark Word кодируются несколько логических состояний, из которых для синхронизации значимы следующие:
Неблокированный (unlocked) — самый частый. Объект свободен или только что вышел из synchronized-блока.
Лёгкая/тонкая блокировка (thin lock) — один поток вошёл в synchronized без конкуренции.
Тяжёлая/раздутая блокировка (inflated lock) — используется при конкуренции или вызове wait()/notify().
Отмечен для сборки мусора (marked for GC) — временное состояние во время работы сборщика мусора.
Хэш сохранён (hash stored) — после вызова System.identityHashCode().
Смещённая заглушка (biased stub) — историческое примечание: эта оптимизация (1998-2021) "резервировала" объект за потоком, но была полностью удалена из OpenJDK в Java 18, так как в многопоточных приложениях отмена смещения стоила дороже выгоды.
Смещённый заголовок (displaced header) — временное состояние при переходе между другими состояниями.
Важное уточнение: Конкретные битовые паттерны (например, 01 для unlocked) зависят от реализации JVM. HotSpot, OpenJ9 и GraalVM могут использовать разные комбинации битов для одних и тех же концептуальных состояний.
// Примерный вид записи в Mark Word:
// 1. НЕБЛОКИРОВАННЫЙ (01) — "свободен для аренды"
// Пример: 0x0000000000000001
// ↑↑ последние биты = 01
// 2. ЛЁГКАЯ БЛОКИРОВКА (00) — "занято, но ключ простой"
// Пример: 0x000070000d65f468
// ↑↑ последние биты = 00
// 3. ТЯЖЁЛАЯ БЛОКИРОВКА (10) — "занято, очередь у двери"
// Пример: 0x00007f8c8400d1a2
// ↑↑ последние биты = 10
Как JVM выбирает состояние блокировки
Логика выбора стратегии блокировки в псевдокоде (концептуально, детали различаются между JVM):
State selectLockingStrategy(Object obj, Thread thread) {
// Проверяем, не помечен ли объект для GC
if (isMarkedForGC(obj)) return State.GC_MARKED;
// Проверяем, есть ли сохранённый хэш
if (hasIdentityHash(obj)) return State.HASH_STORED;
// Смотрим историю использования объекта
LockingProfile profile = getProfile(obj);
if (profile.isSingleThreaded()) {
// Один поток → тонкая блокировка
return attemptThinLock(obj, thread);
} else if (profile.hasWaiters()) {
// Есть wait() → тяжёлая блокировка
return inflateToHeavyLock(obj);
} else if (profile.isHighlyContended()) {
// Много конкурентов → тяжёлая с оптимизациями
return createAdaptiveLock(obj);
}
// Дефолт: начинаем с тонкой
return attemptThinLock(obj, thread);
}#Java #middle #monitor #synchronized #mark_word
👍1🤯1
Жизненный цикл объекта при синхронизации
Сценарий 1: Один поток, нет конкуренции
Псевдокод и реализация может плюс-минус отличаться в каждом из видов JVM
Сценарий 2: Появляется конкуренция
Процесс инфляции (упрощённо):
#Java #middle #monitor #synchronized #mark_word
Сценарий 1: Один поток, нет конкуренции
Псевдокод и реализация может плюс-минус отличаться в каждом из видов JVM
// Простейший случай — один поток синхронизируется на объекте
Object lock = new Object();
// До синхронизации:
// Mark Word = 0x0000000000000001 (неблокированный)
synchronized(lock) {
// 1. JVM проверяет последние биты: 01
// 2. Пытается сделать CAS (сравнить-и-заменить):
// Было: 0x0000000000000001
// Стало: 0x000070000d65f468
// ↑ указатель на стек потока | 00
// 3. Успех! Поток вошёл в блок
counter++; // Критическая секция
}
// После выхода:
// Mark Word снова = 0x0000000000000001
// (лёгкая блокировка снимается)
Что важно: В этом сценарии не создаётся Monitor! Всё работает через быструю CAS-операцию.
Сценарий 2: Появляется конкуренция
// Два потока хотят один объект
Object sharedLock = new Object();
Thread t1 = new Thread(() -> {
synchronized(sharedLock) {
Thread.sleep(100); // Держит блокировку
}
});
Thread t2 = new Thread(() -> {
// Пытается войти, пока t1 внутри
synchronized(sharedLock) {
// Здесь произойдёт инфляция — переход от легковесной блокировки (состоящей лишь из флагов в Mark Word)
// к использованию полноценного объекта-монитора, способного управлять очередями.
}
});
t1.start();
t2.start();
Процесс инфляции (упрощённо):
T2 видит: Mark Word = [...][00] (лёгкая блокировка от T1)
T2 ждёт немного ("спин"), пробует снова — не получается
JVM создаёт Monitor — полноценный объект-диспетчер
Mark Word меняется на: [указатель на Monitor][10]
T2 встаёт в очередь Monitor'а
#Java #middle #monitor #synchronized #mark_word
👍2
Универсальная концепция Monitor'а
Концептуально монитор — это абстракция, которая для любого объекта в Java должна обеспечивать: владельца, счётчик рекурсий и очереди. Именно эту абстракцию и реализуют по-разному в HotSpot (ObjectMonitor), OpenJ9 (J9Monitor) и других JVM. Хотя реализации разные, примерное наполнение едино для всех JVM:
Различия только в:
Языке реализации (C++ в HotSpot, C/Java-подобные структуры в других)
Расположении в памяти (нативная/Java-куча)
Алгоритмах очередей (LIFO/FIFO/адаптивные)
Имени структуры (ObjectMonitor, J9Monitor, Monitor)
Как это посмотреть в реальном коде
Демонстрация с JOL (Java Object Layout)
Пример вывода:
#Java #middle #monitor #synchronized #mark_word
Концептуально монитор — это абстракция, которая для любого объекта в Java должна обеспечивать: владельца, счётчик рекурсий и очереди. Именно эту абстракцию и реализуют по-разному в HotSpot (ObjectMonitor), OpenJ9 (J9Monitor) и других JVM. Хотя реализации разные, примерное наполнение едино для всех JVM:
УНИВЕРСАЛЬНАЯ КОНЦЕПЦИЯ (работает везде):
[Объект] → [Монитор] → {
- Владелец (owner)
- Счетчик рекурсий (recursions)
- Очередь для synchronized (entry queue)
- Очередь для wait() (wait set)
}
Различия только в:
Языке реализации (C++ в HotSpot, C/Java-подобные структуры в других)
Расположении в памяти (нативная/Java-куча)
Алгоритмах очередей (LIFO/FIFO/адаптивные)
Имени структуры (ObjectMonitor, J9Monitor, Monitor)
Как это посмотреть в реальном коде
Демонстрация с JOL (Java Object Layout)
import org.openjdk.jol.info.ClassLayout;
public class LockDemo {
public static void main(String[] args) throws Exception {
Object obj = new Object();
System.out.println("=== 1. НОВЫЙ ОБЪЕКТ ===");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// Mark Word: 0x0000000000000001 (неблокированный)
synchronized(obj) {
System.out.println("\n=== 2. В SYNCHRONIZED (1 поток) ===");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// Mark Word: 0x000070000d65f468 (лёгкая блокировка)
}
// Создаём конкуренцию
Thread t1 = new Thread(() -> {
synchronized(obj) {
try { Thread.sleep(500); } catch (Exception e) {}
}
});
t1.start();
Thread.sleep(100); // Даём t1 захватить блокировку
synchronized(obj) {
System.out.println("\n=== 3. ПРИ КОНКУРЕНЦИИ ===");
System.out.println(ClassLayout.parseInstance(obj).toPrintable());
// Mark Word: 0x00007f8c8400d1a2 (тяжёлая блокировка)
}
}
}
Пример вывода:
=== 1. НОВЫЙ ОБЪЕКТ ===
java.lang.Object object internals:
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x0000000000000001 (non-biasable)
8 4 (object header: class) 0xf80001e5
=== 2. В SYNCHRONIZED (1 поток) ===
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x000070000d65f468 (thin lock: ...)
8 4 (object header: class) 0xf80001e5
=== 3. ПРИ КОНКУРЕНЦИИ ===
OFF SZ TYPE DESCRIPTION VALUE
0 8 (object header: mark) 0x00007f8c8400d1a2 (inflated)
8 4 (object header: class) 0xf80001e5
#Java #middle #monitor #synchronized #mark_word
👍2