Лекция 5. JMM

JMM делалась давно и не везде было 64 бита. Поэтому long и double не атомарный по железу и тогда надо устраивать синхронизацию. Решили этого не делать и не гарантировать атомарность, хотя в реальности она гарантирована. По JMM доступ атомарен для всех базовых типов, кроме long и double. К volatile long и volatile double атомарен.

На очень старом железе возможно, что напечатает 0xFFFFFFFF00000000:

// before
long l = 0;

// thread 0
l = -1; // 0xFF..F

// thread 1
print(l);

Word tearing

// before
T[] tarr = new T[..];
tarr[0] = tarr[1] = val0;

// thread 0
tarr[0] = val1;

// thread 1
tarr[1] = val1;

// thread 2
join t0, t1;
assert(tarr[0] == tarr[1]);

В JVM обновление двух независимых переменных независимо. Это поддерживается железом.

А что делать с boolean? Будем делать boolean 1 байт. Если хотим эффективные биты, то можно использовать BitSet. Но тогда нет гарантий про word tearing и в аналогичном коде assert может упасть (см. выше)

Модель памяти это компромисс между удобством программирования, сложностью реализации языка и поддержки со стороны железа.

Последовательная согласованность

В целом мы хотим иметь последовательную согласованность, чтобы можно было анализировать чередованием. Но это усложняет локальные оптимизации.

По JMM, если в исполнении нет гонок (нет параллельных конфликтующих операций), то оно последовательно согласованно. Но что такое "параллельно" с точки зрения JMM?

Program order

Программный порядок связывает действия внутри одного потока.

Synchronization actions

Это:

  • volatile чтение/запись
  • Взятие/отпускание блокировки
  • Первое и последнее действие в потоке
  • Запуск потока
  • Действия, обнаруживающее останов потока

Все synchronization actions образую synchronization order. Это линейный порядок и он консистентен с program order. Все чтения в synchronization order видят последние записи в synchronization order.

Synchronizes-with order

SO это очень мощно, мы будем использовать SW - подпорядок SO, ограниченный парными действиями синхронизации, например volatile read/write над одной и той же переменной.

Happens-before order

\(HB = (SW \cup PO)^+\)

Чтения видят либо последнюю запись по HB, либо что-то еще под гонкой.

Final семантика

В конце конструктора происходит freeze action, которое "замораживает" поля.