在主题A中,创建了ArrayList
。它仅从线程A管理。
在线程B中,我想将其复制到新实例。
要求是copyList
不应该失败并且应该返回列表的一致版本(=至少在复制过程中存在一段时间)。
我的方法是:
public static <T> ArrayList<T> copyList(ArrayList<? extends T> list) {
List<? extends T> unmodifiableList = Collections.unmodifiableList(list);
return new ArrayList<T>(unmodifiableList);
}
Q1:这是否满足要求?
Q2:如果不使用Collections.unmodifiableList
并且使用可靠的迭代器和try-catch块,我怎么能这样做呢?
UPD。这是我一年前被问到的一个面试问题。我理解在多线程环境中使用像ArrayList这样的非线程安全集合是个坏主意
答案 0 :(得分:4)
没有。 ArrayList
不是线程安全的,您没有使用明确的synchronization
。
当你执行方法unmodifiableList时,第一个线程可以修改原始列表,你将拥有一个无效的不可修改列表。
我认为最简单的方法如下:
例如:
List<T> l = Collections.synchronizedList(new ArrayList<T>());
...
public static <T> List<T> copyList(List<? extends T> list) {
List<T> copyList = null;
synchronized(list) {
copyList = new ArrayList<T>(list);
}
return copyList;
}
答案 1 :(得分:4)
您应该同步对ArrayList
的访问权限,或将ArrayList
替换为CopyOnWriteArrayList
等并发集合。
如果不这样做,您可能会得到一个不一致的副本。
答案 2 :(得分:2)
绝对无法创建 plain ArrayList 的副本,如果“拥有”线程不提供某些协议如此。
没有任何协议,线程A可以随时修改列表,这意味着线程B 永远不会有机会确保看到列表的一致状态
要实际允许进行一致的复制,线程A 必须确保它所做的任何修改都写入内存并且对其他线程可见。
通常情况下,允许VM按照其认为合适的方式重新排序指令,读取和写入,前提是在执行程序的线程中没有观察到任何差异。例如,这包括通过在CPU寄存器或本地堆栈中保存值来延迟写入。
确保所有内容始终写入主要内容的唯一方法是,线程A执行一条指令,该指令向VM提供重新排序屏障(例如,同步块或易失性字段访问)。
因此,如果没有线程A的某些合作,就无法确保满足上述条件。
绕过这种情况的常见方法是通过仅以安全包装的形式(Collections.synchronizedCollection)使用它来同步对List的访问,或者使用内置了这些保证的List实现(任何类型的并发列表实现)
答案 3 :(得分:1)
Collections.unmodifiableList(...)
的javadoc说,&#34;返回指定列表的不可修改的视图。&#34;
关键词是&#34; view&#34;。这意味着它不复制数据。它所做的只是为给定列表创建一个包装器,其中包含所有抛出异常而不是修改基本列表的mutators。
是的,但我实际上创建了新的ArrayList(Collections.unmodif ...),这不会有效吗?
糟糕!我错过了。如果您要复制列表,那么调用unmodifiableList()
就没有意义了。唯一可以访问不可修改视图的代码是在创建它的同一方法中的代码。您不必担心代码修改列表内容,因为您编写了它。
另一方面,如果您在其他线程更新列表时要复制列表,那么您将需要synchronized
全部。代码可以更新列表的每个地方都需要在synchronized
块中,副本的代码也是如此。当然,所有这些同步块必须在同一个对象上同步。
一些程序员将使用list对象本身作为锁对象。其他人则更喜欢使用单独的物体。
答案 4 :(得分:1)
Q1:这是否满足要求?
如果在使用new ArrayList<T>(unmodifiableList)
复制提供的列表时修改了提供的列表,即使您使用ConcurrentModificationException
将其包装,也会得到Collections.unmodifiableList
因为Iterator
UnmodifiableList
1}}只需调用包装列表的Iterator
,因为它是非线程安全列表,您仍然可以获得ConcurrentModificationException
。
您可以做的确是使用CopyOnWriteArrayList
,因为它是一个线程安全列表实现,当您尝试迭代它时,它会提供List
的一致快照。另一种方法可能是使用new ArrayList<T>(myList)
使其他线程定期为其他线程的安全副本,因为它是修改它的唯一线程我们知道在创建副本时没有其他线程会修改它所以它会安全。
Q2:如果没有
Collections.unmodifiableList
,我怎么能这样做呢? 可能是迭代器和try-catch块?
如上所述Collections.unmodifiableList
在这里没有帮助它使线程安全,对我来说唯一可能有意义的事实恰恰相反:线程A(唯一可以修改列表的线程)创建一个使用ArrayList
安全复制new ArrayList<T>(list)
,然后使用Collections.unmodifiableList(list)
将其未修改的列表推送到其他主题。
一般来说,您应该避免在方法的定义中特别是公共定义中指定实现,您应该只使用接口或抽象类,否则您将向API的用户提供实现细节,这是不期望的。所以这里应该是List
或Collection
而不是ArrayList
。