我已经使用以下代码通过非线程安全对象(这里是ArrayList
)运行多个线程:
import java.time.LocalDateTime;
import java.util.List;
import java.util.ArrayList;
public class A implements Runnable {
String name;
static List<Integer> list = new ArrayList();
private static Object lock = new Object();
A(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 1; i <= 1000; i++) {
list.add(i);
}
System.out.println(list.size());
}
}
由于ArrayList
不是线程安全的,因此我期待此代码产生错误的答案。但相反,我得到了这个错误:
Exception in thread "Thread-1" 1003
2401
2799
3799
java.lang.ArrayIndexOutOfBoundsException: 109
at java.util.ArrayList.add(Unknown Source)
at threads.A.run(A.java:16)5123
at java.lang.Thread.run(Unknown Source)
Exception in thread "Thread-5" java.lang.ArrayIndexOutOfBoundsException: 4164
at java.util.ArrayList.add(Unknown Source)
at threads.A.run(A.java:16)
at java.lang.Thread.run(Unknown Source)
6123
任何人都可以向我解释导致此特定错误的原因吗?
答案 0 :(得分:7)
好吧,您在多线程环境中使用非线程安全集合而没有任何同步。
让我们检查add
方法,在那里你得到例外:
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
当多个线程同时调用此方法时,ensureCapacityInternal(size + 1)
很可能验证是否有足够的空间容纳1个新元素,但是多个线程尝试同时添加一个元素,因此{ {1}}为其中一些人投掷elementData[size++]
。
答案 1 :(得分:6)
ArrayList
不是线程安全的类。
元素的底层存储是Object[]
,它是一个数组。任何数组都需要在编译时预定义的内存中的分配空间。但是,当ArrayList
“想要”添加新元素(超出底层数组边界)时,必须完成几件事(在您不知情的情况下)。底层数组获得新的(增加的)长度。将旧数组的每个元素复制到新数组和,然后添加新元素。因此,当在多线程环境中使用ArrayIndexOutOfBoundsException
时,可以期望ArrayList
异常。
您添加的元素太快,因此ArrayList#add() -> grow() -> newCapacity()
无法计算为所有元素分配内存的正确容量。
private void add(E e, Object[] elementData, int s) {
if (s == elementData.length)
elementData = grow();
elementData[s] = e;
size = s + 1;
}
在某个时间点,s == elementData.length
内的条件ArrayList#add
表示有一个新元素A
的空间。紧接着其他线程将其元素放入列表中。现在A
和elementData[s] = e;
没有空间抛出异常。