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.