Ключевое слово synchronized
Ключевое слово synchronized используется в многопоточном
программировании для защиты критических секций кода от
одновременного доступа нескольких потоков. Когда один поток
входит в синхронизированный блок или метод, он захватывает
монитор указанного объекта (или класса), и все остальные потоки,
пытающиеся войти в этот же блок, вынуждены ждать, пока монитор
не будет освобождён. В качестве монитора для нестатических
методов выступает сам экземпляр объекта (this), для
статических - объект класса (Main.class), а для
синхронизированных блоков - объект, переданный в круглых скобках.
Синтаксис
Синхронизированный метод:
public synchronized void methodName()
{
// critical section
}
Синхронизированный статический метод:
public static synchronized void methodName()
{
// critical section
}
Синхронизированный блок:
synchronized (monitorObject)
{
// critical section
}
Пример
Давайте рассмотрим пример, в котором два потока увеличивают общий счётчик. Без синхронизации результат может быть неверным из-за состояния гонки:
public class Main
{
private static int counter = 0;
public static void main(String[] args) throws InterruptedException
{
Runnable task = () -> {
for (int i = 0; i < 10000; i++)
{
counter++;
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter);
}
}
Результат выполнения кода может быть меньше ожидаемого
20000, например:
17352
Пример
Теперь добавим synchronized-блок, чтобы
защитить операцию инкремента:
public class Main
{
private static int counter = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException
{
Runnable task = () -> {
for (int i = 0; i < 10000; i++)
{
synchronized (lock)
{
counter++;
}
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter);
}
}
Теперь результат выполнения кода всегда корректен:
20000
Пример
Давайте посмотрим, как работает synchronized-метод.
Один поток удерживает монитор, а второй ожидает
освобождения:
public class Main
{
public static void main(String[] args) throws InterruptedException
{
Printer printer = new Printer();
Thread t1 = new Thread(() -> printer.print("Thread 1"));
Thread t2 = new Thread(() -> printer.print("Thread 2"));
t1.start();
t2.start();
t1.join();
t2.join();
}
}
class Printer
{
public synchronized void print(String name)
{
for (int i = 0; i < 3; i++)
{
System.out.println(name + ": " + i);
try
{
Thread.sleep(100);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
}
}
}
}
Результат выполнения кода (потоки не перемешиваются):
Thread 1: 0
Thread 1: 1
Thread 1: 2
Thread 2: 0
Thread 2: 1
Thread 2: 2
Пример
Синхронизированный статический метод блокирует монитор класса, а не экземпляра:
public class Main
{
public static void main(String[] args) throws InterruptedException
{
Thread t1 = new Thread(() -> Counter.increment());
Thread t2 = new Thread(() -> Counter.increment());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Counter.getCount());
}
}
class Counter
{
private static int count = 0;
public static synchronized void increment()
{
for (int i = 0; i < 10000; i++)
{
count++;
}
}
public static int getCount()
{
return count;
}
}
Результат выполнения кода:
20000
Смотрите также
-
ключевое слово
volatile,
которое обеспечивает видимость изменений между потоками -
класс
ReentrantLock,
который предоставляет более гибкую альтернативу synchronized -
класс
AtomicInteger,
который выполняет атомарные операции без блокировок -
метод
join,
который ожидает завершения потока