Что означает ключевое слово synchronized в Java? Объясните простыми словами с примерами
Что такое synchronized и зачем он нужен?
Ключевое слово synchronized в Java решает проблему состояния гонки (race condition). Когда несколько потоков одновременно читают и изменяют одну и ту же переменную, результат становится непредсказуемым. В вашем примере операция count++ на самом деле состоит из трёх шагов: 1) чтение значения count, 2) увеличение на 1, 3) запись обратно. Если два потока выполняют эти шаги одновременно, они могут прочитать одно и то же значение, увеличить его и записать — в итоге одно приращение теряется.
synchronized гарантирует, что только один поток может выполнять помеченный блок или метод в данный момент времени. Остальные потоки будут ждать своей очереди. Это называется взаимным исключением (mutual exclusion). Кроме того, synchronized обеспечивает видимость изменений: после выхода из синхронизированного блока все изменения, сделанные потоком, становятся видны другим потокам.
Как исправить ваш пример?
Самый простой способ — сделать метод increment() синхронизированным. Для этого добавьте ключевое слово synchronized перед объявлением метода:
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
Теперь, когда один поток вызывает increment(), другой поток будет ждать, пока первый не завершит выполнение метода. Это гарантирует, что операция count++ выполняется атомарно (неделимо). Результат станет стабильно равен 2000.
Другие способы синхронизации
Вы также можете синхронизировать не весь метод, а только критическую секцию с помощью блока synchronized. Это бывает полезно, когда метод большой, а синхронизировать нужно только часть:
public void increment() {
// какой-то несинхронизированный код
synchronized (this) {
count++;
}
}
Здесь this — это объект, на котором происходит синхронизация (так называемый монитор). Можно использовать любой объект, но обычно используют this или отдельный объект-замок:
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
Важные моменты
- Не злоупотребляйте: Синхронизация замедляет программу, так как потоки простаивают в очереди.
- Deadlock: Если два потока держат блокировки и ждут друг друга, программа зависает. Избегайте вложенных синхронизированных блоков.
- Альтернативы: Для простых счетчиков лучше использовать
AtomicInteger(из пакетаjava.util.concurrent.atomic), который работает быстрее:
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
private AtomicInteger count = new AtomicInteger(0);
public void increment() {
count.incrementAndGet();
}
public int getCount() {
return count.get();
}
}
AtomicInteger использует низкоуровневые механизмы (CAS-операции), которые не блокируют потоки, а значит, работают эффективнее для простых операций инкремента.
Итог
synchronized — это ваш главный инструмент для защиты общих данных от одновременного доступа. Он гарантирует атомарность и видимость. Начните с синхронизации методов, а по мере роста опыта переходите к более тонким механизмам (блоки, Lock, атомарные классы). Главное — помните: если несколько потоков читают и пишут одну переменную, без синхронизации результат будет непредсказуем.
Альтернативный взгляд: синхронизированные блоки и производительность
Первый ответ отлично объясняет основы. Я добавлю про разницу между синхронизированным методом и блоком, а также про производительность.
Когда вы пишете public synchronized void increment(), вы блокируете весь объект this на всё время выполнения метода. Если у вас есть несколько синхронизированных методов в одном классе, они будут блокировать друг друга, даже если работают с разными данными. Это может снизить производительность.
Лучше использовать синхронизированные блоки с отдельными объектами-замками для разных ресурсов:
public class SafeCounter {
private int count1 = 0;
private int count2 = 0;
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void increment1() {
synchronized (lock1) {
count1++;
}
}
public void increment2() {
synchronized (lock2) {
count2++;
}
}
}
Теперь потоки, работающие с count1 и count2, не мешают друг другу. Это называется тонкозернистой синхронизацией.
Ещё один совет: для коллекций из java.util.concurrent (например, ConcurrentHashMap) синхронизация уже встроена и работает эффективнее, чем ручная. Используйте их вместо синхронизации обычных коллекций.
И помните: синхронизация — это компромисс между безопасностью и производительностью. Всегда старайтесь минимизировать критическую секцию (код внутри synchronized), чтобы потоки меньше ждали.
Ответов пока нет.
Войдите чтобы оставить ответ