Состояние гонки Java HashMap - PullRequest
       18

Состояние гонки Java HashMap

6 голосов
/ 20 октября 2011

Я пытаюсь выяснить, будет ли какое-либо состояние гонки в этом фрагменте кода.Если бы ключ не был «Thread.currentThread», я бы подумал, что да.Но поскольку сама нить является ключевой, как возможно иметь состояние гонки?Никакой другой поток не может обновить тот же ключ в HashMap!

public class SessionTracker {

     static private final Map<Thread,Session>  threadSessionMap = new HashMap<Thread,Session>();

     static public Session get() {
         return threadSessionMap.get(Thread.currentThread());
     }

     static public void set(Session s) {
         threadSessionMap.put(Thread.currentThread(),s);
     }

     static public void reset() {
         threadSessionMap.remove(Thread.currentThread());
     }
}

Ответы [ 5 ]

13 голосов
/ 20 октября 2011

Ответ - да, есть потенциальные условия гонки:

  • при изменение размера HashMap двумя потоками одновременно
  • при случается столкновение .Столкновение может произойти, когда два элемента отображаются на одну и ту же ячейку, даже если они имеют разные хэш-коды.Во время разрешения конфликта может быть условие гонки, и одна добавленная пара ключ / значение может быть перезаписана другой парой, вставленной другим потоком.

Чтобы лучше объяснить, что я имею в виду во втором пункте, япросматривал исходный код HashMap в OpenJdk 7

389        int hash = hash(key.hashCode());
390        int i = indexFor(hash, table.length);

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

Если он занят, он смотрит на следующую ячейку, а затем на следующую, пока не найдет пустую позицию и не вызовет addEntry(), который может даже решить изменить размер массива, если массив загружен больше, чем определенный loadFactor.

Наш table, содержащий записи, является просто вектором Entry, который содержит ключ и значение.

146    /**
147     * The table, resized as necessary. Length MUST Always be a power of two.
148     */
149    transient Entry[] table;

В параллельной среде могут происходить всевозможные злые вещи, например, один поток получает столкновение для ячейки № 5, ищет следующую ячейку (6) и находит ее пустой.

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

2 голосов
/ 20 октября 2011

Не вдаваясь в конкретные детали реализации Hashmap, я бы сказал, что все еще существует вероятность ошибки, учитывая тот факт, что класс Hashmap не безопасен для одновременного доступа.

Хотя я согласен с тем, чтодолжна быть только 1 модификация одиночного ключа за раз, поскольку вы используете currentThread (), все еще существует вероятность того, что несколько потоков будут изменять Hashmap одновременно.Если вы не посмотрите на конкретную реализацию, вы не должны предполагать, что только одновременный доступ к одному и тому же ключу вызовет проблему в Hashmap, и что одновременное изменение различных ключей не будет.

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

1 голос
/ 20 октября 2011

Да, это небезопасно (как уже упоминалось в других ответах). Лучшим решением может быть использование ThreadLocal , который является более естественным способом сохранения локальных данных потока, чем использование Map. У него есть несколько приятных функций, включая значения по умолчанию, и эти значения удаляются при завершении потока.

0 голосов
/ 05 октября 2015

Я согласен с предыдущими ответами, что ваш код не является потокобезопасным, и хотя использование ConcurrentHashMap решит вашу проблему, это идеальный вариант использования для ThreadLocal .

Краткое введение в ThreadLocal:

ThreadLocal внутренне будет содержать разные экземпляры класса для каждого потока, который обращается к ThreadLocal, поэтому для решения любых проблем параллелизма.Кроме того (в зависимости от ситуации это может быть хорошо / плохо), значение, сохраненное в ThreadLocal, может быть доступно только потоку, который заполнил это значение в первую очередь.Если текущий поток впервые обращается к ThreadLocal, значение будет нулевым.

Простой пример ThreadLocal, который содержит строковые значения:

private static ThreadLocal<String> threadVar = new ThreadLocal<>();

public void foo() {
    String myString = threadVar.get();

    if (myString == null) {
        threadVar.set("some new value");
        myString = threadVar.get();
    }
}
0 голосов
/ 02 февраля 2015

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

...