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

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

Does the unmodifiable wrapper for java collections make them thread safe?

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


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

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


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

1
2
3
4
if(queue.Count > 0)
{
   queue.Add(...);
}

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


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

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

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


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

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

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

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

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


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


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

1
2
3
4
static class UnmodifiableSet<E> extends UnmodifiableCollection<E>
                 implements Set<E>, Serializable;

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

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


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

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


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

  • 仍然有对原始可修改列表的引用。
  • 该列表可能会通过迭代器进行访问。
  • 如果仅打算按索引从ArrayList中读取,则可以假定这是线程安全的。

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


    推荐阅读