根据定义,只读操作是线程安全的。只有在至少有一个线程修改数据时才会发生竞争条件或数据竞争。
因此,如果您将值放入映射中,在创建从映射中读取值的线程之前,那么操作是完全安全的。如果你从不修改 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
希望对你有帮助