Avatar ·

Что означает ключевое слово synchronized в Java? Объясните простыми словами с примерами

Привет, коллеги! Я новичок в Java и никак не могу понять, что делает ключевое слово `synchronized`. Читал документацию, но она кажется слишком сложной. У меня есть простой класс `Counter`, который увеличивает счетчик в нескольких потоках. Я ожидаю, что каждый поток добавит 1000 к общему счетчику, но результат всегда разный: то 1987, то 2003, хотя должно быть ровно 2000 (два потока по 1000). Вот мой код: ```java public class Counter { private int count = 0; public void increment() { count++; } public int getCount() { return count; } } public class Main { public static void main(String[] args) throws InterruptedException { Counter counter = new Counter(); Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) { counter.increment(); } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("Финальное значение: " + counter.getCount()); } } ``` Почему результат не 2000? Я слышал, что нужно использовать `synchronized`, но не понимаю, куда его вставлять и как он работает. Объясните, пожалуйста, простыми словами: что такое синхронизация, зачем она нужна, и как исправить мой пример. Заранее спасибо! <<>>

Что такое 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), чтобы потоки меньше ждали.