多个线程同时向非同步的 ArrayList 对象添加元素会导致什么问题?
尝试使用具有多个线程的静态ArrayList运行一些实验,但找不到太多。
在这里,我期待在多线程环境中不同步ArrayList或类似Objects的大部分副作用。
任何显示副作用的好例子都是值得注意的。 感谢。
下面是我的小实验,顺利进行,没有任何例外。
我也想知道为什么它没有抛出任何ConcurrentModificationException
?
import java.util.ArrayList;
import java.util.List;
public class Experiment {
static List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("A " + i);
new Thread(new Worker(list, "" + i)).start();
}
}
}
class Worker implements Runnable {
List<Integer> al;
String name;
public Worker(List<Integer> list, String name) {
this.al = list;
this.name = name;
}
@Override
public void run() {
while (true) {
int no = (int) (Math.random() * 10);
System.out.println("[thread " + name + "]Adding:" + no + "to Object id:" + System.identityHashCode(al));
al.add(no);
}
}
}
答案 0 :(得分:3)
调整列表大小以容纳更多元素时,通常会遇到问题。查看ArrayList.add()
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
如果没有同步,则数组的大小将在调用ensureCapacityInternal
和实际元素插入之间发生变化。这最终会导致ArrayIndexOutOfBoundsException
被抛出。
以下是产生此行为的代码
final ExecutorService exec = Executors.newFixedThreadPool(8);
final List<Integer> list = new ArrayList<>();
for (int i = 0; i < 8; i++) {
exec.execute(() -> {
Random r = new Random();
while (true) {
list.add(r.nextInt());
}
});
}
答案 1 :(得分:2)
通过将元素添加到多个线程使用的非清除ArrayList中,您可以根据需要获取空值来代替实际值。
这是因为下面是ArrayList类的代码。
public boolean add(E e) {
ensureCapacity(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList类首先检查其当前容量,如果需要则增加其容量(默认容量为10和下一个增量(10 * 3)/ 2)并将默认类级别值放在新空间中。
假设我们正在使用两个线程,并且两者同时添加一个元素,并发现默认容量(10)已被填充并且是时间增加其容量。首先,threadOne来增加ArrayList的大小默认值使用ensureCapacity方法(10+(10 * 3/2))并将其元素放在下一个索引(size = 10 + 1 = 11),现在新大小为11.现在第二个线程来增加相同ArrayList的大小使用ensureCapacity方法(10+(10 * 3/2))再次使用默认值并将其元素放在下一个索引(size = 11 + 1 = 12),现在新大小为12.在这种情况下,您将在索引处获得null 10这是默认值。
以上是相同的代码。
package com;
import java.util.ArrayList;
import java.util.List;
public class Test implements Runnable {
static List<Integer> ls = new ArrayList<Integer>();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Test());
Thread t2 = new Thread(new Test());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(ls.size());
for (int i = 0; i < ls.size(); ++i) {
System.out.println(i + " " + ls.get(i));
}
}
@Override
public void run() {
try {
for (int i = 0; i < 20; ++i) {
ls.add(i);
Thread.sleep(2);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
输出:
39
0 0
1 0
2 1
3 1
4 2
5 2
6 3
7 3
8 4
9 4
10 null
11 5
12 6
13 6
14 7
15 7
16 8
17 9
18 9
19 10
20 10
21 11
22 11
23 12
24 12
25 13
26 13
27 14
28 14
29 15
30 15
31 16
32 16
33 17
34 17
35 18
36 18
37 19
38 19
运行两三次后,你会在索引10和某个时间16点获得空值。
如上所述,通过noscreenname回答,您可能会从此代码中获取ArrayIndexOutOfBoundsException。如果删除Thread.sleep(2),它将频繁生成。
请根据需要检查数组的总大小。根据代码,它应该是40(20 * 2),但每次都会有所不同。
注意:您可能需要多次运行此代码才能生成一个或多个方案。
答案 2 :(得分:0)
这是一个简单的例子:我将10个项目中的1000个项目添加到列表中。你可能期望最后有10,000件物品,但你可能不会。如果你多次运行它,你每次可能会得到不同的结果。
如果您想要ConcurrentModificationException,可以在创建任务的循环之后添加for (Integer i : list) { }
。
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(10);
List<Integer> list = new ArrayList<> ();
for (int i = 0; i < 10; i++) {
executor.submit(new ListAdder(list, 1000));
}
executor.shutdown();
executor.awaitTermination(1, TimeUnit.SECONDS);
System.out.println(list.size());
}
private static class ListAdder implements Runnable {
private final List<Integer> list;
private final int iterations;
public ListAdder(List<Integer> list, int iterations) {
this.list = list;
this.iterations = iterations;
}
@Override
public void run() {
for (int i = 0; i < iterations; i++) {
list.add(0);
}
}
}
答案 3 :(得分:0)
ConcurrentModificationException。在这里,您只需从多个线程中向您列出数据,这些线程不会产生异常。尝试使用迭代器在某些地方&amp;你会看到异常。
在下面找到修改后的示例以生成ConcurrentModificationException。
public class Experiment {
static List<Integer> list = new ArrayList<Integer>();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
System.out.println("A " + i);
new Thread(new Worker(list, "" + i)).start();
}
Iterator<Integer> itr = list.iterator();
while(itr.hasNext()) {
System.out.println("List data : " +itr.next());
}
}
}