ArrayList.add(int index,E元素)线程是否不安全



我之前的问题引出了这个问题。

ArrayList的add函数线程安全吗?

我制作了一个带有以下类的示例应用程序

import java.util.ArrayList;
import java.util.List;

public class ThreadTest
{
public static List<DummyObject> list = null;
public static boolean isLoaded = false;
public static void main(String [] args)
{
MyThread t1 = new MyThread(1);
MyThread t2 = new MyThread(2);
t1.start();
t2.start();
}
public static void loadObject(){
if(isLoaded){
return;
}
isLoaded = false;
try{
list = new ArrayList<DummyObject>();
for(int i=0;i<10;i++){
list.add(i,new DummyObject());
}}
catch(Exception e){
e.printStackTrace();
}
isLoaded = true;
}
}

这些是我的线程

public class MyThread extends Thread
{
int threadNumber ;
public MyThread(int threadNumber)
{
this.threadNumber = threadNumber;
}
@Override
public void run()
{
try {
sleep(10-threadNumber);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.println("Running Thread: " + threadNumber);
ThreadTest.loadObject();
if(ThreadTest.isLoaded){
System.out.println(ThreadTest.list);
for(int i=0;i<ThreadTest.list.size();i++){
if(ThreadTest.list.get(i)==null){
throw new NullPointerException();
} 
}
}else {
try {
sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

这是我的模拟类

public class DummyObject {
}

尽管我无法复制我在上一个问题中得到的Null Pointer Exception,但有时我会得到这个错误

Exception in thread "Thread-1" java.lang.IndexOutOfBoundsException: Index: 1, Size: 10
at java.util.ArrayList.add(ArrayList.java:367)
at ThreadTest.loadObject(ThreadTest.java:25)
at MyThread.run(MyThread.java:20)

Form ArrayList代码这是抛出错误的行:

if (index > size || index < 0)
throw new IndexOutOfBoundsException(
"Index: "+index+", Size: "+size);

但正如我们从Exception中看到的,索引是1,大小是10,所以如果条件得到满足,就不可能。所以,我的假设是正确的,arrayList的add函数是线程不安全的,还是这里发生了其他事情?

来自文档:

(这个类大致相当于Vector,只是它是不同步的。)

您需要自己实现同步,或者更好的是,使用像Vector这样的同步容器。

在您的代码中,有两个线程运行同一段代码(loadObject),其中访问/修改了几个静态值。您需要确保每次访问都是以同步的方式进行的。您有2个线程,因此您分配了两次ThreadTest.list字段,因此其中一个分配是无用的,但更重要的是,在该列表丢失之前,可能会在该列表中插入一些值,因此这些值也会丢失。

在分配列表之前,您应该确保列表没有被分配。

您还可能遇到isLoaded字段的问题,导致列表中有10个以上的元素。

简单回答,不,它不是线程安全的。从JavaDoc

请注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,并且至少有一个线程在结构上修改了该列表,则必须对其进行外部同步。(结构修改是指添加或删除一个或多个元素,或显式调整支持数组大小的任何操作;仅仅设置元素的值并不是结构修改。)这通常是通过对自然封装列表的某个对象进行同步来实现的。如果不存在这样的对象,则应使用Collections.synchronizedList方法"包装"列表。这最好在创建时完成,以防止意外地对列表进行不同步的访问:

List list = Collections.synchronizedList(new ArrayList(...));

一般来说,Java中的现代集合都不是线程安全的。如果你只是想让它们不爆炸,你可以按照JavaDoc中的建议使用Collections.synchronizedList。然而,值得注意的是,这只是意味着一次只有一个线程可以访问集合。这确实使它安全,但可能会导致线程被阻塞的问题。

如果您正试图获得高并发性,那么您真的想看看java.util.concurrent包。这为您提供了像ArrayBlockingQueue这样的好类,这使得这种线程切换非常容易。更好的是,看看Executor实现,它们可以为您处理很多这种复杂性。

我已经放置了ArrayList类的add方法的实际代码。

/**
* Inserts the specified element at the specified position in this
* list. Shifts the element currently at that position (if any) and
* any subsequent elements to the right (adds one to their indices).
*
* @param index index at which the specified element is to be inserted
* @param element element to be inserted
* @throws IndexOutOfBoundsException {@inheritDoc}
*/
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++;
}

这里,这个方法声明它不是线程安全的,因为elementData

private transient Object[] elementData;

,它是更新的,在多线程环境中,它可能会导致关键问题。

最新更新