不可变对象的真正好处是什么

时间:2018-08-27 09:45:05

标签: concurrency immutability

我总是听到人们说,在使用多个线程时,管理不可变对象比较容易,因为当一个线程访问一个不可变对象时,不必担心另一个线程正在更改它。

那么,如果我拥有公司中所有雇员的不可变列表,并且雇用了新雇员,那会发生什么?在这种情况下,不可变列表必须被复制,并且其新副本必须包含另一个雇员对象。然后,对雇员列表的引用应定向到新列表。

发生这种情况时,列表本身不会更改,但是对该列表的引用也会更改,因此代码“看到”了不同的数据。

如果是这样,我不明白为什么使用多线程时不变的对象会使我们的生活更轻松。我想念什么?

2 个答案:

答案 0 :(得分:3)

并发更新可变数据的主要问题是,线程可能会感知到来自不同版本的变量值,即在谈到单个更新时,新旧值的混合,形成不一致的状态,从而违反了这些变量的不变性

例如,参见Java的ArrayList。它有一个int字段,该字段保存当前大小,并引用一个数组,该数组的元素是对所包含对象的引用。这些变量的值必须满足某些不变性,例如如果大小不为零,则数组引用从不为null,并且数组长度始终大于或等于该大小。当看到这些变量的不同更新的值时,这些不变量不再成立,因此线程可能会看到一个列表内容,该内容从未以这种形式存在或由于虚假异常而失败,报告了应该是不可能的非法状态(例如{{1} }或NullPointerException)。

请注意,线程安全或并发数据结构仅解决了有关数据结构内部的问题,因此操作不会再因虚假异常而失败(关于集合的状态,我们尚未讨论所包含元素的状态) ,但是在这些集合上进行迭代或查看一个以上任何形式的包含元素的操作仍可能会观察到与包含元素有关的不一致状态。这也适用于先检查后反模式,其中应用程序先对条件进行检查(例如,使用ArrayIndexOutOfBoundeException),然后再对其进行操作(例如,获取,添加或删除元素),而条件可能会在两者之间发生变化。

相比之下,在不可变数据结构上运行的线程可能在其过时的版本上工作,但是属于该结构的所有变量都彼此一致,反映了相同的版本。执行更新时,您无需考虑排除其他线程,这根本没有必要,因为其他线程看不到新的数据结构。发布新版本的整个任务简化为将根引用发布到数据结构的新版本的任务。如果无法停止其他线程处理旧版本,则可能发生的最糟糕的情况是,在最坏的情况下,您可能必须随后使用新数据重复操作,换句话说,就是性能问题。

这可以与带有垃圾回收的编程语言一起使用,因为它们允许新数据结构引用旧对象,而无需替换仍在使用的对象,而只需替换更改的对象(及其父对象)即可。而不是。

答案 1 :(得分:0)

这里是一个示例:(a)我们有一个不可变的列表,(b)我们有编写器线程将元素添加到列表中,以及(c)1000个读取线程在不更改列表的情况下读取了列表。

它将在没有锁的情况下工作。

如果我们有多个写程序线程,我们仍然需要写外观。如果必须从列表中删除条目,则需要读写锁。

它有价值吗?不知道。