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

Pre*_*mar 8 java multithreading synchronization arraylist

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

尝试使用具有多个线程的静态ArrayList运行一些实验,但找不到多少.

在这里,我期待在多线程环境中不同步ArrayList或类似对象的大部分副作用.

任何显示副作用的好例子都是可以理解的.谢谢.

下面是我的小实验,顺利进行,没有任何例外.

我也想知道它为什么不抛出任何东西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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

nos*_*ame 6

调整列表大小以容纳更多元素时,通常会遇到问题。看执行ArrayList.add()

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}
Run Code Online (Sandbox Code Playgroud)

如果没有同步,则数组的大小将在调用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());
        }
    });
}
Run Code Online (Sandbox Code Playgroud)


Ami*_*arg 5

通过将元素添加到多线程使用的未同步的 ArrayList 中,您可能会根据需要获得空值代替实际值。

发生这种情况是因为 ArrayList 类的以下代码。

 public boolean add(E e) {
        ensureCapacity(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
     }
Run Code Online (Sandbox Code Playgroud)

ArrayList 类首先检查其当前容量,如果需要,则增加其容量(默认容量为 10,下一个增量为 (10*3)/2)并将默认类级别值放入新空间。

假设我们正在使用两个线程,并且两个线程同时添加一个元素,发现默认容量(10)已满,是时候增加它的容量了。首先,线程一个来使用默认值增加 ArrayList 的大小ensureCapacity 方法(10+(10*3/2)) 并将其元素放在下一个索引处(size=10+1=11),现在新的大小是 11。现在第二个线程来了,用默认值增加同一个 ArrayList 的大小再次使用 ensureCapacity 方法(10+(10*3/2)) 并将其元素放在下一个索引处 (size=11+1=12),现在新大小为 12。在这种情况下,您将在索引 10 处获得 null,即默认值。

这是上面的相同代码。

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)
  1. 运行两三次后,有时在索引 10 和有时在 16 处获得空值。

  2. 正如上面 noscreenname 的回答中提到的,您可能会从此代码中获得 ArrayIndexOutOfBoundsException。如果您删除 Thread.sleep(2) 它将频繁生成。

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

注意:您可能需要多次运行此代码才能生成一个或多个场景。


ass*_*ias 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);
    }
  }
}
Run Code Online (Sandbox Code Playgroud)