【问题标题】:Modify outer object from inside a thread从线程内部修改外部对象
【发布时间】:2017-04-17 07:51:42
【问题描述】:
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;
            }
        });
    }
}
  • 修改Callable内的HashMap安全吗?

  • hashmap 应该是final(或者volatile)吗?如果是,为什么?

  • 我应该使用HashMap以外的结构,比如ConcurrentHashMap或SynchronizedMap,为什么?

我正在尝试掌握 Java 概念,所以请多多包涵

【问题讨论】:

    标签: java multithreading java-threads


    【解决方案1】:

    在Callable里面修改HashMap安全吗?

    没有。如果您使用的是线程池,我假设您计划让更多这些可调用对象并行运行。任何时候从多个线程访问具有可变状态的对象,这都是线程不安全的。如果你同时从两个线程写入一个线程不安全的 hashmap,它的内部结构将被破坏。如果您在另一个线程同时写入时从线程不安全的哈希映射中读取,则您的读取线程将读取垃圾。这是一种众所周知且经过广泛研究的情况,称为 Race Condition,对其的描述将完全超出此答案的范围。如需更多信息,请阅读 Race Condition on Wikipedia 或 2008 年回答的另一个问题:Stackoverflow - What is a Race Condition。

    hashmap 是否应该是最终的(或者可能是 volatile)?如果是,为什么?

    出于您的目的,它不需要是最终的,但将任何可以成为最终的东西都做成最终的总是一个好习惯。

    它不需要是易变的,因为:

    • 如果您要使其成为 volatile,那么您将对它的引用设为 volatile,但引用永远不会改变,改变的是它的内容,而 volatile 与这些无关。

    • 线程池确保call() 将在fooBarMap = new HashMap&lt;&gt;() 之后执行。 (如果你想知道为什么这样的事情会成为一个问题,谷歌搜索“内存边界”。)

    我应该使用 HashMap 以外的结构,比如 ConcurrentHashMap 或 SynchronizedMap,为什么?

    当然。因为,正如我之前写的,任何时候从多个线程访问具有可变状态的对象,这都是线程不安全的。而ConcurrentHashMap、SynchronizedMap、synchronize 等正是为了处理线程不安全的情况而存在的。

    【讨论】:

    • 谢谢。如果我只是 writing 到 HashMap,您能否描述一下线程不安全如何影响代码?此外,我不确定我是否理解为什么 HashMap 不需要是 volatile 的(可能是因为 fooBarMap 持有的引用永远不会改变?),但我会确保阅读更多相关信息。另一件事要提一下,在我正在编写的逻辑中,在所有线程完成处理之前,没有其他线程会从 fooBarMap 读取。那么在那种情况下我还需要ConcurrentHashMap 吗?
    • 正确,对地图的引用永远不会改变,所以不需要volatile。至于其余的,我只是修改了我的答案。
    • 我同意这个答案中的所有内容,但我认为问题代码需要稍作修改......不是所有提供的实现都在单个方法的范围内吗?在没有类/实例变量的单一方法中,不会有共享状态,因此不会出现并发问题。
    • @tomkab 不在单一方法的范围内。该方法使用ExecutorCompletionService 生成一个线程,该线程运行一个子方法,该子方法尝试从不同的线程访问父方法的状态。这只是一个例子,所以它只产生一个线程,但实际上他会产生很多。
    • 再次感谢,我对现在的工作感觉更自在了。
    【解决方案2】:

    Hashmap 不应该是最终的,因为您正在多次修改它(在 for 循环中)。

    如果你把它变成final,你可能会得到一个错误。

    【讨论】:

    • 由于它是在匿名内部类中使用的,它必须是最终的或有效的最终
    • 而且,它是final与你是否可以修改它的内容完全没有关系。
    猜你喜欢
    • 2023-03-27
    • 1970-01-01
    • 1970-01-01
    • 2021-08-18
    • 1970-01-01
    • 2022-01-15
    • 1970-01-01
    • 2013-11-16
    • 2010-12-21
    相关资源
    最近更新 更多