static final Collection<String> FILES = new ArrayList<String>(1);
for (final String s : list) {
new Thread(new Runnable() {
public void run() {
List<String> file2List = getFileAsList(s);
FILES.addAll(file2List);
}
}).start();
}
这个集合变得非常大,但代码工作得很完美。我以为我会得到一个并发的修改异常,因为FILES列表必须扩展它的大小,但它从未发生过。
此代码100%线程安全吗?
代码需要12秒才能加载,一些线程同时添加元素。
我尝试先创建线程然后运行它们,但我得到了相同的结果(时间和正确性)
答案 0 :(得分:3)
不,代码不是线程安全的。它可能会或可能不会抛出ConcurrentModificationException
,但最终可能会丢失元素或添加两次元素。将列表更改为
Collection<String> FILES = Collections.synchronizedList(new ArrayList<String>());
可能已经是一个解决方案,假设最耗时的部分是getFilesAsList
方法(并且不将结果元素添加到FILES
列表中)。
getFileAsList
访问硬盘时,您应该执行详细的性能测试。多线程硬盘驱动器访问可能比单线程驱动器慢,因为硬盘驱动器头可能必须绕驱动器跳转而不能将数据作为连续块读取。
编辑:回应评论:该程序“很可能”不时产生ArrayIndexOutOfBoundsExceptions
:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
public class ConcurrentListTest
{
public static void main(String[] args) throws InterruptedException
{
for (int i=0; i<1000; i++)
{
runTest();
}
}
private static void runTest() throws InterruptedException
{
final Collection<String> FILES = new ArrayList<String>(1);
// With this, it will always work:
// final Collection<String> FILES = Collections.synchronizedList(new ArrayList<String>(1));
List<String> list = Arrays.asList("A", "B", "C", "D");
List<Thread> threads = new ArrayList<Thread>();
for (final String s : list)
{
Thread thread = new Thread(new Runnable()
{
@Override
public void run()
{
List<String> file2List = getFileAsList(s);
FILES.addAll(file2List);
}
});
threads.add(thread);
thread.start();
}
for (Thread thread : threads)
{
thread.join();
}
System.out.println(FILES.size());
}
private static List<String> getFileAsList(String s)
{
List<String> list = Collections.nCopies(10000, s);
return list;
}
}
但当然,没有严格的保证。如果它没有为你创造这样的例外,你应该考虑玩彩票,因为你必须非常幸运。
答案 1 :(得分:1)
是的,您需要一个并发列表来阻止ConcurrentModificationException
。
以下是一些在Java中初始化并发列表的方法:
Collections.newSetFromMap(new ConcurrentHashMap<>());
Collections.synchronizedList(new ArrayList<Object>());
new CopyOnWriteArrayList<>();
答案 2 :(得分:1)
此代码100%线程安全吗?
这个代码是0%线程安全的,即使是最弱的交错操作标准。你正在改变数据竞争下的共享状态。
你绝对需要某种并发控制;但是,并发集合是否是正确的选择并不明显。一个简单的synchronizedList
可能更适合该法案,因为您有大量处理,然后快速转移到累加器列表。锁定不会引起太大争议。
答案 3 :(得分:1)
即使您只添加元素,也不是线程安全。如果您只增加了FILES
,如果您的集合不是线程安全的,那么还存在一些多重访问问题。
当集合超出它们的大小时,它必须被复制到新的空间,并且在那一刻你可能遇到并发访问的问题,因为在一个线程将执行copy
的东西......另一个可以是尝试在同一时间添加元素到该集合,调整大小由内部arraylist实现完成,但它根本不是线程安全的。
检查代码并假设当收集容量已满时,多个线程执行它。
private int size; //it is not thread safe value!
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e; //size is not volatile value it might be cached by thread
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1); // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}
public void ensureCapacity(int minCapacity) {
if (minCapacity > 0)
ensureCapacityInternal(minCapacity);
}
private void ensureCapacityInternal(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
当集合需要超出其容量并将现有元素复制到新的内部数组时,您可能会遇到多重访问的实际问题,而且大小也不是线程,因为它不是易失性的,并且可以被某些线程缓存并且你可以有覆盖:)这就是为什么它可能不是线程安全的,即使你只使用非同步集合上的添加操作。
您应该考虑使用FILES=new CopyOnWriteArrayList();
或FILES= Collections.synchronizedList(new ArrayList());
,其中添加操作是线程安全的。