synchronized

Ключевое слово synchronized — основной встроенный механизм Java для защиты разделяемых данных. Когда несколько потоков обращаются к одному объекту, операции чтения и записи могут перемежаться непредсказуемым образом (race condition). synchronized гарантирует, что в защищённом участке кода в любой момент времени работает только один поток.

Под капотом каждый объект в Java имеет невидимый монитор (mutex). Поток, вошедший в синхронизированный метод или блок, захватывает монитор и освобождает его при выходе. Другие потоки, претендующие на тот же монитор, переходят в состояние BLOCKED. synchronized также устанавливает отношение happens-before — изменения, сделанные внутри блока, видны другим потокам, входящим в синхронизированный блок на том же мониторе.

Синтаксис

// синхронизированный метод
public synchronized void inc() { /* ... */ }

// синхронизированный блок
synchronized (lockObject) {
    // критическая секция
}

// статический метод — монитор на Class-объекте
public static synchronized void log(String s) { /* ... */ }

Пример 1. Race condition без synchronized

public class RaceDemo {
    private static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Runnable task = () -> {
            for (int i = 0; i < 100_000; i++) counter++;
        };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("counter = " + counter);
    }
}

Output (значение почти всегда меньше 200000):

counter = 137245

Пример 2. Синхронизированный метод

public class SafeCounter {
    private int value = 0;

    public synchronized void inc() { value++; }
    public synchronized int get() { return value; }

    public static void main(String[] args) throws InterruptedException {
        SafeCounter c = new SafeCounter();
        Runnable task = () -> { for (int i = 0; i < 100_000; i++) c.inc(); };
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start(); t2.start();
        t1.join(); t2.join();
        System.out.println("counter = " + c.get());
    }
}

Output:

counter = 200000

Пример 3. Синхронизированный блок

public class BlockSync {
    private final Object lock = new Object();
    private int reads = 0;

    public void readSensor(int value) {
        // тяжёлая часть — без блокировки
        int processed = value * 2;

        // короткая критическая секция
        synchronized (lock) {
            reads++;
            System.out.println("sensor " + reads + " = " + processed);
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BlockSync b = new BlockSync();
        Thread t1 = new Thread(() -> b.readSensor(10));
        Thread t2 = new Thread(() -> b.readSensor(20));
        t1.start(); t2.start();
        t1.join(); t2.join();
    }
}

Output:

sensor 1 = 20
sensor 2 = 40

Пример 4. Deadlock

public class DeadlockDemo {
    private static final Object LOCK_A = new Object();
    private static final Object LOCK_B = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (LOCK_A) {
                sleep(100);
                synchronized (LOCK_B) {
                    System.out.println("t1: оба захвачены");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (LOCK_B) {
                sleep(100);
                synchronized (LOCK_A) {
                    System.out.println("t2: оба захвачены");
                }
            }
        });

        t1.start(); t2.start();
    }

    private static void sleep(long ms) {
        try { Thread.sleep(ms); } catch (InterruptedException ignored) {}
    }
}

Output:

(программа зависает — классический deadlock)

Пример 5. Статический synchronized

public class GlobalLogger {
    public static synchronized void log(String msg) {
        System.out.println("[" + Thread.currentThread().getName() + "] " + msg);
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            for (int i = 0; i < 3; i++) log("шаг " + i);
        };
        new Thread(r, "A").start();
        new Thread(r, "B").start();
    }
}

Output (строки не перемешиваются между потоками):

[A] шаг 0
[A] шаг 1
[A] шаг 2
[B] шаг 0
[B] шаг 1
[B] шаг 2

Подводные камни

Предупреждение

Deadlock возникает, когда два потока захватывают мониторы в разном порядке. Правило: всегда блокируйте ресурсы в одном и том же порядке.

Предупреждение

Слишком крупная критическая секция убивает параллелизм. Держите внутри synchronized только то, что действительно требует защиты.

Совет

Не синхронизируйтесь на String-литералах, Integer.valueOf() или на this в публичных классах — внешний код может случайно перехватить тот же монитор. Используйте приватный private final Object lock = new Object();.

См. также

Примечание

Материал подготовлен на основе раздела «Concurrency» из Oracle Java Tutorials (docs.oracle.com/javase/tutorial/essential/concurrency/) и распространяется в соответствии с Oracle Free Documentation License. Тексты и примеры кода написаны заново на русском языке для AlashEd Wiki.