Изменить внешний объект из потока

public void fooAndBar() {
    HashMap<Foo, Bar> fooBarMap = new HashMap<>();
    CompletionService completionService = new ExecutorCompletionService(exec);

    for(int i=0; i<10; i++) {
        completionService.submit(new Callable() {
            @Override
            public Void call() throws Exception {
                fooBarMap.put(new Foo(i), new Bar(i));
                return null;
            }
        });
    }
}
  • Безопасно ли изменять HashMap внутри Callable?

  • Если хэш-карта должна быть final (или, может быть volatile) и если да, то почему?

  • Должен ли я использовать структуру, кроме HashMap, что-то вроде ConcurrentHashMap или же SynchronizedMap и почему?

Я пытаюсь понять концепции Java, поэтому, пожалуйста, потерпите меня

2 ответа

Решение

Безопасно ли изменять HashMap внутри Callable?

Нет. Если вы используете пул потоков, я предполагаю, что вы планируете, чтобы больше этих вызываемых элементов выполнялось параллельно. Каждый раз, когда к объекту с изменяемым состоянием обращаются из более чем одного потока, это небезопасно для потока. Если вы пишете в небезопасный хеш-файл из двух потоков одновременно, его внутренняя структура будет повреждена. Если вы читаете из небезопасного hashmap, в то время как другой поток пишет в него одновременно, ваш поток чтения будет читать мусор. Это очень хорошо известная и широко изученная ситуация, известная как "состояние гонки", описание которой будет полностью выходить за рамки этого ответа. Для получения дополнительной информации читайте о состоянии гонки в Википедии или о другом вопросе, на который был дан ответ в 2008 году: Stackru - Что такое состояние гонки.

Должна ли хеш-карта быть окончательной (или, возможно, изменчивой) и если да, то почему?

Для ваших целей он не обязательно должен быть окончательным, но всегда полезно делать окончательное все, что можно сделать окончательным.

Это не должно быть изменчивым, потому что:

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

  • поток потоков гарантирует, что call() будет выполнен после fooBarMap = new HashMap<>(), (Если вам интересно, почему такая вещь может вызывать беспокойство, поищите в Google "границу памяти".)

Должен ли я использовать структуру, отличную от HashMap, что-то вроде ConcurrentHashMap или SynchronizedMap и почему?

Определенно. Потому что, как я писал ранее, каждый раз, когда объект с изменяемым состоянием доступен из более чем одного потока, это небезопасно для потока. А также ConcurrentHashMap, SynchronizedMap, synchronizeи т. д. существуют именно для того, чтобы позаботиться о небезопасных ситуациях.

Hashmap не должен быть окончательным, так как вы изменяете его несколько раз (из цикла for).

Если вы сделаете это окончательно, вы можете получить ошибку.

Другие вопросы по тегам