синхронизировано с объектом, похоже, что оно не синхронизировано

Я запускаю программу, которая содержит следующие классы (не только, но и те, которые имеют отношение к вопросу)

В классе Results у меня есть синхронизированная LinkedHashMap, например:

private static Map<Integer,Result>    resultsHashMap=Collections.synchronizedMap(new LinkedHashMap<Integer, Result>());

и метод получения:

public static Map<Integer,Result> getResultsHashMap() {
        return resultsHashMap;
}

Кроме того, внутри моего класса Result есть конструктор с этим синхронизированным кодом:

public Result(){
    synchronized (Lock.lock) {
        uniqueIdResult++;
    }
}

и синхронизированный метод получения как таковой:

public static int getUniqueIdResult() {
    synchronized (Lock.lock) {
        return uniqueIdResult;
    }

}

uniqueIdResult определяется следующим образом:

private static int uniqueIdResult=0;

Также у меня есть класс Lock, состоящий из этого объекта:

public static final Lock lock=new Lock();

Теперь, это важный вопрос, которым я занимаюсь. В моей программе у меня есть следующие две строки, которые создают результат и помещают его в HashMap.

Result result = new Result();
Results.getResultsHashMap().put(Result.getUniqueIdResult(), result);

Я пытаюсь запустить свою программу с разным количеством потоков. Когда он запускается с 1 потоком, вывод такой, как я ожидаю (конкретно, но не обязательно важно, Results.resultsHashMap содержит 433 ключа, что и должно быть, и ключи начинаются с 1).

Но когда я запускаю его с другим количеством потоков, он дает другой результат. Например, работа с 6 потоками каждый раз дает разное количество ключей, иногда 430, иногда 428, иногда 427 и т. д., и начальный ключ не всегда связан с общим количеством ключей (например, total_number_of_keys-starting_key_number+1, который мне показалось в начале какая-то закономерность, но понял что это не так)

Итерация такая:

int counterOfResults=0;
    for (Integer key : Results.getResultsHashMap().keySet()) {
        System.out.println(key + " " + Results.getResultsHashMap().get(key));
        counterOfResults++;
    }
    System.out.println(counterOfResults);

Кроме того, при синхронизации метода получения для получения hashMap без синхронизации создания Result и вставки в hashMap вывод с несколькими потоками дает неверный вывод.
Кроме того, при синхронизации только одна из строк (создание Result и размещение в hashMap), вывод не является согласованным для нескольких потоков.

Однако, когда я синхронизирую обе эти строки (создание Result и размещение на карте) следующим образом:

Result result;
    synchronized (Lock.lock) {
         result = new Result(currentLineTimeNationalityNameYearofbirth.getName(),currentLineTimeNationalityNameYearofbirth.getTime(),citycompetionwas,date,distance,stroke,gender,kindofpool);
        Results.getResultsHashMap().put(Result.getUniqueIdResult(), result);
    }

результат идеален, независимо от того, сколько потоков я использую.

Кроме того, я отмечу, что вывод печатается только после завершения всех потоков с использованием метода join для всех созданных потоков.

Итак, мой вопрос:
Насколько мне известно, перед синхронизацией двух строк (создание Result и помещение в hashMap) все мои критические разделы, например, изменение и получение uniqueIdResult, получение resultsHashMap (как я уже упоминал, я также пытался синхронизировать этот метод получения) синхронизируются с одним и тем же объектом, плюс я добавил еще один безопасный подход при размещении hashMap с Collections.synchronizedMap, что, насколько мне известно, должно сделать hashMap потокобезопасным.

Почему же тогда результат не такой, как я ожидаю? Где проблема с безопасностью?


person Achi Even-dar    schedule 29.10.2015    source источник
comment
начиная с 4 (по крайней мере, когда я проверял) что это значит? Похоже, вы не уверены, с какого числа оно началось. Пожалуйста, предоставьте воспроизводимый код.   -  person weston    schedule 29.10.2015
comment
Я имел в виду, что когда я запускал программу в 4 потока (около 10 раз), это был результат. Поскольку вывод не всегда согласован (и с 6 потоками я заметил разные результаты при разных исполнениях), я предполагаю, что это также могло быть другое число. В любом случае, я удалил эту часть из своего вопроса, так как это может быть неясно.   -  person Achi Even-dar    schedule 29.10.2015


Ответы (1)


Вокруг этих строк нет исключений:

Result result = new Result();
Results.getResultsHashMap().put(Result.getUniqueIdResult(), result);

Если у вас есть 4 потока, все они могут выполнить первую строку (что увеличит переменную uniqueIdResult в четыре раза), а затем все выполнить вторую строку (в этот момент все они увидят одно и то же возвращаемое значение из getUniqueIdResult()). Это объясняет, как ваши ключи могут начинаться с 4, когда у вас есть 4 (или более) потока.

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

Вероятно, вам следует удалить приращение из конструктора класса Result и вместо этого сделать это в методе getUniqueIdResult:

public static int getUniqueIdResult() {
    synchronized (Lock.lock) {
        return ++uniqueIdResult;
    }
}

(После этого больше нет необходимости создавать экземпляры Result).

person davmac    schedule 29.10.2015