多个线程同时向非同步的ArrayList对象添加元素会导致什么问题?

时间:2016-07-22 13:14:36

标签: java multithreading arraylist synchronization

多个线程同时向非同步的 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);
        }
    }
}

4 个答案:

答案 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
  1. 运行两三次后,你会在索引10和某个时间16点获得空值。

  2. 如上所述,通过noscreenname回答,您可能会从此代码中获取ArrayIndexOutOfBoundsException。如果删除Thread.sleep(2),它将频繁生成。

  3. 请根据需要检查数组的总大小。根据代码,它应该是40(20 * 2),但每次都会有所不同。

  4. 注意:您可能需要多次运行此代码才能生成一个或多个方案。

答案 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)

仅当使用Iterator迭代相同列表时修改列表时才会出现

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());
        }
    }   
}