# V6074. Non-atomic modification of volatile variable.

Анализатор обнаружил неатомарное изменение 'volatile' переменной, которое может привести к состоянию гонки.

Известно, что использование модификатора 'volatile' гарантирует, что все потоки будут видеть актуальное значение соответствующей переменной. К этому можно добавить, что модификатор 'volatile' используется для того, чтобы указать JVM, что все операции присвоения этой переменной и все операции чтения из неё должны быть атомарными.

Можно посчитать, что пометки переменных как 'volatile' будет достаточно, чтобы безопасно их использовать в многопоточном приложении! Но что будет, если изменять 'volatile' переменную, будущее значение которой зависит от текущего?

К таким операциям можно отнести:

- var++, ‑‑var, ...
- var += smt, var *= smt, ...
- ...

Рассмотрим использование 'volatile' переменной в качестве счетчика (counter++).

    class Counter
    {
      private volatile int counter = 0;
      ....
      public void increment()
      {
        counter++; // counter = counter + 1
      }
      ....
    }

Такая операция выглядит как одна операция, но в действительности это целая последовательность операций чтения-изменения-записи. Здесь и кроется состояние гонки.

Предположим, что 2 потока одновременно работают с объектом класса Counter и выполняют инкремент переменной 'counter' (10):

[counter == 10, temp == 10] Поток N1 считывает значение 'counter'во временную переменную.

[counter == 10, temp == 11] Поток N1 изменяет временную переменную.

[counter == 10, temp == 10] Поток N2 считывает значение 'counter'во временную переменную.

[counter == 11, temp == 11] Поток N1 записывает временную переменную в 'counter'.

[counter == 11, temp == 11] Поток N2 изменяет временную переменную.

[counter == 11, temp == 11] Поток N2 записывает временную переменную в 'counter'.

Ожидалось значение переменной 'counter' равное 12 (а не 11), так как 2 потока выполнили инкремент над одной и той же переменной. Также возможна ситуация, когда потоки выполнят инкремент друг за другом, и в таком случае все будет так, как и ожидалось. Как итог, раз на раз не приходится!

Чтобы избежать подобного поведения неатомарных операций для разделяемых переменных, можно использовать:

- Блок 'synchronized',
- Классы из пакета java.util.concurrent.atomic,
- Функциональность блокировок из пакета java.util.concurrent.locks

Пример корректного кода:

    class Counter
    {
      private volatile int counter = 0;
      ....
      public synchronized void increment()
      {
        counter++;
      }
      ....
    }

или

    class Counter
    {
      private final AtomicInteger counter = new AtomicInteger(0);
      ....
      public void increment()
      {
        counter.incrementAndGet();
      }
      ....
    }

Выявляемые диагностикой ошибки классифицируются согласно ГОСТ Р 71207–2024 как критические и относятся к типу: Ошибки при работе с многопоточными примитивами (интерфейсами запуска потоков на выполнение, синхронизации и обмена данными между потоками и пр.).

Данная диагностика классифицируется как:

CWE-567 CERT-VNA02-J

Взгляните на примеры ошибок, обнаруженных с помощью диагностики V6074.