Введение в синхронизацию в Java
Два потока могут одновременно читать и менять одно
поле объекта. Каждый шаг "прочитать, прибавить,
записать" в исходнике выглядит цельным, но JVM
между инструкциями переключает потоки, и часть
приращений теряется.
Состояние гонки чаще всего замечают на
общем счетчике: каждый поток много раз увеличивает
переменную, а после join сумма ниже ожидаемой.
Проблема не в арифметике, а в том, что операции
накладываются во времени.
Синхронизация ограничивает доступ к
критическому участку: в один момент его меняет
только один поток. В Java для этого есть ключевое
слово synchronized, интерфейс Lock
и классы из java.util.concurrent.
Проверка на двух потоках по тысяче приращений без
защиты обычно дает число ниже 2000:
public class Main {
private static int hits = 0;
public static void main(String[] args) throws InterruptedException {
Runnable step = () -> {
for (int i = 0; i < 1000; i++) {
hits++;
}
};
Thread left = new Thread(step, "left");
Thread right = new Thread(step, "right");
left.start();
right.start();
left.join();
right.join();
System.out.println(hits);
}
}
Запускайте пример несколько раз: итог плавает, хотя логика простая. В следующих уроках закроем участок монитором, явным замком и атомарными типами.