# V6070. Unsafe synchronization on an object.

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

Проблема заключается в том, что если производить синхронизацию по:

- 'this',
- объектам целочисленных классов оберток (Byte, Short, Integer, Long),
- объекту класса обертки для логического типа (Boolean),
- объекту класса String,

то это может приводить к потенциальным тупикам и недетерминированному поведению.

Причиной этому может служить то, что вышеперечисленные объекты могут повторно использоваться в разных частях программы.

Суть проблемы синхронизации по приведенным объектам состоит в том, что к объекту, используемому для блокировки, имеется общий доступ. Такой объект может быть использован для блокировки в другом месте без ведома разработчика, использовавшего объект для блокировки в первый раз. Это, в свою очередь, и создаёт вероятность возникновения взаимоблокировки на один и тот же объект.

Приведём синтетический пример взаимоблокировки при синхронизации по 'this':

    class SynchroThis
    {
      void doSmt()
      {
        synchronized(this)
        {
          // do smt
        }
      }
    }

    ....
    SynchroThis obj = new SynchroThis();
    synchronized(obj)
    {
      Thread t = new Thread(() -> obj.doSmt());
      t.start();
      t.join();
    }
    ....

В результате программа никогда не завершится, т.к. происходит deadlock по экземпляру класса SynchroThis (первая блокировка в основном потоке по 'obj', вторая - в потоке 't' по 'this').

Для того, чтобы избежать возможных взаимных блокировок, в качестве объекта блокировки стоит использовать, например, приватное поле:

    class A
    {
      private final Object lock = new Object();
      void foo()
      {
        synchronized(lock)
        {
          // do smt
        }
      }
    }

Рассмотрим синтетический пример синхронизации по объекту типа Byte:

    class FirstClass
    {
      private final Byte idLock;
      ....
      public FirstClass(Byte id, ....)
      {
        idLock = id;
        ....
      }
      ....
      public void calculateFromFirst(....)
      {
        synchronized (idLock)  // <=
        {
          ....
        }
      }
    }

    class SecondClass
    {
      private final Byte idLock;
      ....
      public SecondClass(Byte id, ....)
      {
        idLock = id;
        ....
      }
      ....
      public void calculateFromSecond(....)
      {
        synchronized (idLock)  // <=
        {
          ....
        }
      }
    }

Обусловим, что поток N1 оперирует объектом класса 'FirstClass', а поток N2 - 'SecondClass'.

Теперь давайте рассмотрим сценарий:

- У объекта класса 'FirstClass' поле 'idLock' равно 100, у объекта класса 'SecondClass' тоже 100;
- Поток N1 начинает выполнять метод 'calculateFromFirst', и какое-то время выполняется;
- Поток N2 (сразу же следом) начинает выполнять метод 'calculateFromSecond'.

Итак, у нас 2 разных потока выполняют совершенно разную логику программы для разных объектов. Что же получится? А получится то, что поток N2 будет находиться в состоянии ожидания до тех пор, пока поток N1 не закончит работу в синхронизированном блоке по объекту 'idLock'. Почему же так получается?

Как и все объекты, переменные созданные с помощью классов оберток будут храниться в куче. У каждого такого объекта будет свой адрес в куче. Но есть небольшой нюанс, который нужно всегда учитывать. Целочисленные классы обертки, полученные при помощи автоупаковки, со значением в диапазоне [-128..127] кэшируются JVM. Поэтому такие обертки с одинаковыми значениями в этом диапазоне будут являться ссылками на один объект.

Так и получается в нашем случае. Синхронизация производится по одному и тому же объекту в памяти, чего вовсе и не ожидалось.

Также, помимо целочисленных классов оберток, не следует использовать для синхронизации объекты классов:

- Boolean;
- String (так как может получиться, что синхронизация будет производиться по строке, которая хранится в пуле строк).

Использование синхронизации по таким объектам небезопасно. Рекомендуется использовать вышеописанный способ с приватным полем, но если по каким-либо причинам Вам это не подходит, то создавайте объекты явно при помощи конструктора. Такой способ гарантирует, что у объектов будут разные адреса. Пример безопасного кода:

    class FirstClass
    {
      private final Byte idLock;
      ....
      public FirstClass(Byte id, ....)
      {
        idLock = new Byte(id);
        ....
      }
      ....
      public void calculateFromFirst(....)
      {
        synchronized (idLock)
        {
          ....
        }
      }
    }
    ....

Дополнительную информацию можно посмотреть здесь.

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

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

CWE-662 CERT-LCK01-J