Java集合的不可修改包装器会使它们线程安全吗?

时间:2020-03-05 19:00:10  来源:igfitidea点击:

我需要使ArrayLists线程的ArrayList安全。我也不能让客户对集合进行更改。不可修改的包装器会使其线程安全吗,或者我需要在集合上使用两个包装器?

解决方案

回答

不可修改的包装器仅阻止更改其适用的列表的结构。如果此列表包含其他列表,并且我们有尝试修改这些嵌套列表的线程,那么我们将无法避免发生并行修改风险。

回答

通过查看"集合"源,看起来"不可修改"不会使其同步。

static class UnmodifiableSet<E> extends UnmodifiableCollection<E>
                 implements Set<E>, Serializable;

static class UnmodifiableCollection<E> implements Collection<E>, Serializable;

同步的类包装器中有一个互斥对象来完成同步的部分,因此看起来我们需要同时使用两者。还是自己动手!

回答

这取决于。包装器只会阻止对其包装的集合进行更改,而不是对集合中的对象进行更改。如果我们有一个ArrayLists的ArrayList,则全局列表及其每个元素列表都需要分别包装,并且我们可能还需要为这些列表的内容做些事情。最后,我们必须确保不更改原始列表对象,因为包装器仅阻止通过包装器引用而不是原始对象的更改。

在这种情况下,我们不需要同步包装器。

回答

在以下情况下这是必需的:

  • 仍然有对原始可修改列表的引用。
  • 该列表可能会通过迭代器进行访问。

如果仅打算按索引从ArrayList中读取,则可以假定这是线程安全的。

如有疑问,请选择同步包装器。

回答

不知道我是否理解我们要做什么,但我会说大多数情况下的答案是"否"。

如果我们将ArrayList和ArrayList设置为ArrayList和ArrayList,则在创建后就永远不能更改外部列表和内部列表(并且在创建过程中,只有一个线程可以访问内部列表和外部列表),它们可能是由包装程序保护的线程安全,外部列表和内部列表以无法修改的方式包装)。 ArrayList上的所有只读操作很可能是线程安全的。但是,Sun不能保证它们是线程安全的(也不适用于只读操作),因此即使它可能现在就可以工作,也可能会在将来中断(如果Sun创建了一些内部数据缓存以更快地访问它们)例子)。

回答

我相信,因为UnmodifiableList包装器将ArrayList存储到final字段,所以只要创建包装器后未修改列表,包装器上的任何读取方法都将看到构建包装器时的列表。只要包装器内部的可变ArrayList不被修改(包装器无法防范)。

回答

在一个相关主题上,我看到了几条建议,建议使用同步收集以实现线程安全。
使用集合的同步版本并不能使其成为"线程安全的",尽管在组合两个操作时每个操作(插入,计数等)都受到互斥锁的保护,无法保证它们会自动执行。
例如,以下代码不是线程安全的(即使具有同步队列):

if(queue.Count > 0)
{
   queue.Add(...);
}

回答

根据定义,不可变对象是线程安全的(假设没有人保留对原始集合的引用),因此不需要同步。

使用Collections.unmodifiableList()包装外部ArrayList
防止客户端更改其内容(从而使其成为线程)
安全),但内部ArrayList仍然可变。

也使用Collections.unmodifiableList()包装内部ArrayList
防止客户更改其内容(从而使它们
线程安全),这就是我们所需要的。

让我们知道此解决方案是否会引起问题(开销,内存使用情况等);
其他解决方案可能适用于问题。 :)

编辑:当然,如果列表被修改,它们不是线程安全的。我假设没有进一步的编辑。

回答

如果安全地发布了不可修改的视图,并且在发布不可修改的视图之后,则永远都不会修改可修改的原始文档(包括集合中递归包含的所有对象!),它将是线程安全的。

如果要继续修改原始图,则可以创建集合对象图的防御性副本并返回该图的不可修改视图,或者使用固有的线程安全列表作为开头,然后返回该图的不可修改视图那。

如果我们以后仍然打算不同步地访问List,则不能返回unmodifiableList(synchonizedList(theList));如果在多个线程之间共享可变状态,则所有线程在访问该状态时必须在相同的锁上进行同步。