— Привіт, Аміго!

Є така велетенська тема, що називається Java Memory Model. Знати її досконало тобі поки не обов'язково, але дещо почути про неї буде корисно.

Щоб усунути всі можливі проблеми, в Java змінили механізм роботи пам'яті. Тепер пам'ять не просто ділиться на локальний кеш потоку та глобальну пам'ять, механізм став ще краще.

— І складніше!

— Так, краще і складніше. Це як літак. Літати на ньому краще, ніж йти пішки, але складніше. Спробую пояснити тобі нову ситуацію максимально спрощенно.

Ось що придумали. До коду додали механізм синхронізації локальної пам'яті потоків, який називається «happens before» (дослівно: «траплялося перед»). Придумали низку правил/умов, при настанні яких пам'ять синхронізується і оновлюється до актуального стану.

Приклад:

Порядок Потік 1 Потік 2
1
2

101
102
103
104
105

201
202
203
204
205
public int y = 1;
public int x = 1;

x=2;
synchronized(mutex)
{
 y = 2;
}
потік чекає на вивільнення м'ютекса — mutex

synchronized(mutex)
{
 if (y == x)
 System.out.println("YES");
}

Одна з таких умов – захоплення м'ютекса, що звільнився. Якщо м'ютекс звільнився і його знову захоплено, то перед захопленням обов'язково виконається синхронізація пам'яті. Потік 2 побачить «найновіші» значення змінних x та y, навіть якщо не оголошувати їх volatile.

— Як цікаво. І багато таких умов?

— Достатньо, ось деякі умови синхронізації пам'яті:

  • В межах одного потоку будь-яка команда happens-before (тобто «трапляється перед») будь-якою операцією, що слідує за нею в початковому коді.
  • Звільнення лока (unlock) happens-before захопленням того ж лока (lock).
  • Вихід з synchronized блоку/методу happens-before вхід до synchronized блоку/методу на тому ж моніторі.
  • Запис volatile поля happens-before читання того ж самого volatile поля.
  • Завершення методу run екземпляра класу Thread happens-before вихід з методу join() чи повернення false методом isAlive() екземпляром того ж потоку.
  • Виклик методу start() екземпляра класу Thread happens-before початок методу run() екземпляра того ж потоку.
  • Завершення конструктора happens-before початок методу finalize() цього класу.
  • Виклик методу interrupt() у потоці happens-before, коли потік виявив, що цей метод викликано або через викидання винятку InterruptedException, або за допомогою методів isInterrupted() чи interrupted().

— Тобто все трохи складніше, ніж я думав?

— Так, трохи складніше…

— Дякую, є над чим замислитися.

— Не дуже заморочуйся. Прийде час, і ти сам усе зрозумієш. Поки що тобі краще розібратися в основах, аніж лізти у нетрі того, як влаштована Java-машина.

— О_о. Мда. Про деякі речі краще не знати.