为了账号安全,请及时绑定邮箱和手机立即绑定

值可变的并发安全容器的线程安全

值可变的并发安全容器的线程安全

12345678_0001 2021-11-11 16:40:13
假设我有一个确保线程安全(检索的完全并发)的映射,其中检索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在这种情况下访问有什么问题吗?是否存在无法按预期执行的情况?
查看完整描述

2 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

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


所以如果将值放入映射中,在创建从映射中读取值的线程之前,则操作是完全安全的。如果你从不修改 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 变得不稳定,像这样:


class Foo { 

    public volatile Object bar;

}

并且要非常小心你如何操作 bar 以及里面的东西,如果它是某个类的实际对象。


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


它有一个关于这个的部分,用非常好的例子很好地解释了它。


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


我希望它有帮助


查看完整回答
反对 回复 2021-11-11
?
慕标琳琳

TA贡献1830条经验 获得超9个赞

它应该作为将值放入和取出并发哈希图应该建立发生之前的关系(请参阅ConcurrentHashMap.get() 是否保证通过不同的线程看到先前的 ConcurrentHashMap.put() ?)。消费者线程应该看到在地图插入之前的 bar 值。

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


查看完整回答
反对 回复 2021-11-11
  • 2 回答
  • 0 关注
  • 178 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信