Удаление объекта из Map между действиями проверки и получения элемента в другом потоке

Rostislav Dugin

Допустим, имеется ConcurrentHashMap, с которой работает много потоков. В один момент из нее могут удалить или изменить объект. Может ли произойти, что во время проверки условия или действия объект удалится?

Для примера код:

if (mOrdersMap.containsKey(id) && mOrdersMap.get(id).getStatus() == WAIT) {
    mOrdersMap.get(id).handle();
}

Этот участок кода я разбил бы на три действия:

  1. Проверка условия 1: mOrdersMap.containsKey(id).
  2. Проверка условия 2: mOrdersMap.get(id).getStatus() == WAIT.
  3. Действие с объектом: mOrdersMap.get(id).handle().

Есть ли вероятность того, что между первым и вторым или вторым и третьим действиями из Map удалят этот элемент (если именно в этот момент может поступить команда удаления)?

И насколько оправдана вот такая "проверка" в случае, если работа идет в многопоточной среде, где к Map обращаются в большом количестве:

synchronized(mOrdersMap) {
    //Действия из примера кода выше
}
1 ответ

Rostislav Dugin

Может. Ничего не мешает другому потоку удалить из хэш-таблицы данный ключ id между действиями mOrdersMap.containsKey(id) и mOrdersMap.get(id).getStatus(). В этом можно убедиться на примере:

private static volatile boolean isWorking = true;
private static final Map<integer, object=""> mOrdersMap = new ConcurrentHashMap<>();

public static void main(String[] args)
{
    final int id = 1;
    int tries = 0;
    int errorsCount = 0;
    new Thread(new Runnable()
    {
        @Override
        public void run()
        {
            while (isWorking)
            {
                mOrdersMap.remove(id);
                mOrdersMap.put(id, this);
            }
        }
    }).start();
    while (isWorking)
    {
        if (mOrdersMap.containsKey(id))
        {
            if (mOrdersMap.get(id) == null)
            {
                errorsCount++;
            }
            tries++;
        }
        isWorking = (errorsCount < 1000 && tries < 1000 * 1000);
    }
    System.out.println("Tries: " + tries + ", Errors: " + errorsCount);
}
</integer,>

Один поток постоянно добавляет и удаляет объект в mOrdersMap по заданному ключу, а другой - проверяет полученное методом get значение в случае, если метод containsKey вернул true.

Результаты получаются вроде такого:

Tries: 9487, Errors: 1000

При использовании же synchronized (mOrdersMap):

synchronized (mOrdersMap)
{
    mOrdersMap.remove(id);
    mOrdersMap.put(id, this);
}

и

synchronized (mOrdersMap)
{
    if (mOrdersMap.containsKey(id))
    {
        if (mOrdersMap.get(id) == null)
        {
            errorsCount++;
        }
        tries++;
    }
}

ситуация кардинально меняется:

Tries: 1000000, Errors: 0

containsKey и 2 get-a можно объединить в один get:

Object object = mOrdersMap.get(id);
if (object != null)
{
    tries++;
}

В таком случае ошибок тоже не будет. Однако, как и в первом случае, возможны ситуации, при которых действия будут производиться с объектом, которого уже нет в хэш-таблице (но он в ней был на момент вызова метода get).

licensed under cc by-sa 3.0 with attribution.