Java并发性实践:3.5.4有效不可变对象:我们是否需要线程安全的收集容器来有效不可变对象

Java并发性实践:3.5.4有效不可变对象:我们是否需要线程安全的收集容器来有效不可变对象,java,multithreading,concurrency,immutability,Java,Multithreading,Concurrency,Immutability,第3.5.4节讨论了有效的不可变对象,也就是说,一旦一个对象被安全地完全构造,它的状态就不会被任何代码路径的任何代码改变 戈茨爵士举了一个例子: 例如,Date是可变的,但是如果您像使用日期一样使用它 immutable您可能能够消除可能导致 否则,当跨线程共享[共享]日期时,必须使用此选项。 假设您希望维护一个映射,其中存储每个用户的上次登录时间 用户: publicmap lastLogin= Collections.synchronizedMap(新的HashMap()); 如果将日期值

第3.5.4节讨论了有效的不可变对象,也就是说,一旦一个对象被安全地完全构造,它的状态就不会被任何代码路径的任何代码改变

戈茨爵士举了一个例子:

例如,
Date
是可变的,但是如果您像使用日期一样使用它 immutable您可能能够消除可能导致 否则,当跨线程共享[共享]日期时,必须使用此选项。 假设您希望维护一个映射,其中存储每个用户的上次登录时间 用户:

publicmap lastLogin=
Collections.synchronizedMap(新的HashMap());
如果将
日期
值放置在
Map
,然后在
synchronizedMap
实施足以安全发布
日期
值,并且 访问它们时不需要额外的同步

我无法理解的一点是,当我们可以简单地使用不安全的
Map
时,为什么我们要使用
synchronizedMap
并承担其内部锁定的额外开销,因为毕竟我们将有效地在其中放置不可变的
Date
对象-这意味着,一旦正确、完整地构建和发布,它将不再变异。因此,即使
Map
本身是不安全的,当其他线程从不安全的
Map
中检索到它时,在任何代码路径中都不会有代码同时变异任何
Date
实例


总之,,有效不可变对象的前提并不需要任何线程安全的容器,因为我们不应该在有效不可变对象的任何代码路径中有任何变异代码。

如果您使用不同步的
可变映射
并在
线程之间共享它,那么您将有两个
线程安全
问题:
可见性
原子性
Thread-1
不知道
Thread-2
是否删除了
Map条目
,或者是否将其值替换为新的
Date
对象

// not atmoic and doesn't guarantee visiblity
if(map.contains(key)){
 map.put(key,newDate); 
}

原文中的关键短语是“完全构造和发布”。“发布”,特别是指使一个线程创建的对象对其他线程可见,并且当该对象不是真正不可变的时,必须安全地执行(Google“Java安全发布”)

如果没有同步,Java不能保证一个线程对变量的更新会被其他线程看到,或者以什么顺序看到更新

在大多数计算机体系结构中,为所有线程提供共享内存的一致视图相对昂贵。通过在显式同步时不要求线程具有一致视图,Java允许线程在需要时获得一致视图,或者在不需要时获得最佳性能


此外,以上所有内容都忽略了一个非常现实的可能性,即程序可能需要出于其他原因(例如,防止同时更新损坏映射本身)同步对映射的访问。

是,
日期本身是不可变的。但是您正在从多个线程调用map.put()。而
map
也不是一成不变的。您正在添加它。如果没有同步,读取地图的一个线程将不能保证看到另一个线程所做的更改。@fukanchik:
Date
不是不变的。你在说什么?@Makoto阅读了这个问题:
日期是可变的,但是如果你把它当作不可变的,你也许可以消除锁定
@fukanchik:我想强调的主要一点是,当某个东西不是不可变的时候,你不想称它为不可变的<代码>日期
是不可更改的;你可能想用的措辞是“实际上是不可变的”。我在最后一段中特别明白你的意思。即使在这个特定的示例中日期对象被用作有效的不可变对象,包含这些日期对象的容器也不是线程安全的。这可能会导致并发环境中的可见性、脏读和各种不一致。
// not atmoic and doesn't guarantee visiblity
if(map.contains(key)){
 map.put(key,newDate); 
}