【问题标题】:Thread safety of concurrency safe containers where values are mutable值可变的并发安全容器的线程安全
【发布时间】:2018-10-11 11:13:35
【问题描述】:

假设我有一个确保线程安全(检索的完全并发)ConcurrentHashMap<String, Foo> 的映射,其中检索的值是一些 可变 对象

class Foo { 
    public Object bar;
}

Foo 值只会由单个线程构造,添加到映射一次,然后永远不会修改。操作可能如下所示:

Foo foo = new Foo();
foo.bar = "Test";
concurrentMap.add("key", foo);

在一个单独的线程中,通过查看映射中的值来执行工作(假设这里的值是预先正确设置的)

System.out.println(concurrentMap.get("key").bar);

在这种情况下访问Foo::bar 有什么问题吗?是否存在无法按预期执行的情况?

【问题讨论】:

    标签: java concurrency nested thread-safety


    【解决方案1】:

    它应该作为将值放入和取出并发哈希映射应该建立之前发生的关系(参见Is ConcurrentHashMap.get() guaranteed to see a previous ConcurrentHashMap.put() by different thread?)。消费者线程应该看到插入地图之前的 bar 值。

    就我个人而言,我会努力使 Foo(和 bar)对象不可变(如果它们从不改变,为什么不呢?)。

    【讨论】:

    • 同意在设计时考虑到不变性。更多的是一个理论问题,而不是我试图解决的问题。在考虑 ConcurrentHashMap<String, HashMap...>ConcurrentHashMap<String, ConcurrentHashMap...> 时出现了问题
    【解决方案2】:

    根据定义,只读操作是线程安全的。只有在至少有一个线程修改数据时才会发生竞争条件或数据竞争。

    因此,如果您将值放入映射中,在创建从映射中读取值的线程之前,那么操作是完全安全的。如果你从不修改 bar 或它在 foo 中的引用,你也应该没问题。在这种情况下,您甚至不需要并发映射。一张普通的地图就可以了。

    但是,如果 bar 被修改或对 bar 的引用在 foo 中被修改,您可能会得到意想不到的结果。这是一个可能出错的例子。假设 bar 是long

    class Foo { 
        public long bar;
    }
    

    你有线程 1 在做:

    Foo foo = concurrentMap.get("key");
    .....
    ..... /// some code
    System.out.println(foo.bar);
    

    后台还有另一个线程这样做:

    Foo foo = concurrentMap.get("key");
    .....
    long newBar = foo.bar + 1;
    foo.bar = newBar;
    

    这里你有一个直接的比赛条件。

    现在如果线程 2 实际上只是这样做:

     Foo foo = concurrentMap.get("key");
     .....
     long newBar = rand.nextLong();
     foo.bar = newBar;
    

    您没有竞争条件,但您有数据竞争,因为 long 是 64 位的,编译器可能会执行对 long 和 double 作为两个操作的赋值。

    还有很多可能出错的场景,如果不非常小心,真的很难推理它们

    所以至少,你应该让 bar volatile,像这样:

    class Foo { 
        public volatile Object bar;
    }
    

    如果它是某个类的实际对象,请非常小心如何操作 bar 以及里面的内容。

    如果您想了解有关数据竞争的更多信息并更深入地了解竞争条件,您应该查看这个优秀的课程https://www.udemy.com/java-multithreading-concurrency-performance-optimization/?couponCode=CONCURRENCY

    它有一个关于这个的部分,它解释得很好,有很好的例子。

    您还应该查看官方 Java 内存模型文档https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4

    希望对你有帮助

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-05-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-21
      相关资源
      最近更新 更多