多线程问题实操

Posted by OOFTF Blog on June 16, 2022

List 多线程问题

1
2
3
4
5
6
7
8
9
10
11
12
        ArrayList<String> data = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            new Thread(() -> data.add(Thread.currentThread().getName() + finalI)).start();
        }
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                for (String item : data) {
                    Log.e(Thread.currentThread().getName(), "item:"+item);
                }
            }).start();
        }

上述代码目前已知会产生两个多线程问题

1. ArrayIndexOutOfBoundsException

1
2
    java.lang.ArrayIndexOutOfBoundsException: length=33; index=33
        at java.util.ArrayList.add(ArrayList.java:468)

查看 add 源码

1
2
3
4
5
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

假如同时多个线程执行到 ensureCapacityInternal(size + 1); 但是没有执行 elementData[size++] = e; 就会导致 size+1 这个数据并不是最终个数,导致上述问题。

解决方案

  1. 使用线程安全的 List
  2. 手动添加锁

    2. ConcurrentModificationException

这是因为在遍历过程中有其他线程对数据进行了修改

解决方案

  1. 使用线程安全的 List
  2. 手动添加锁

ArrayList copyData = new ArrayList<>(data); 先将原数据进行 copy 再进行遍历。这种方式,并不能解决多线程问题,因为 new ArrayListt<>(data) 这一步本身就会对 data 进行遍历期间就有可能产生多线程问题 第三种情况报错堆栈位

1
2
3
4
5
6
7
java.util.ConcurrentModificationException
	at java.util.AbstractList$SimpleListIterator.next(AbstractList.java:62)
	at java.util.AbstractList$SubAbstractList$SubAbstractListIterator.next(AbstractList.java:201)
	at java.util.AbstractList$SubAbstractList$SubAbstractListIterator.next(AbstractList.java:201)
	at java.util.AbstractCollection.toArrayList(AbstractCollection.java:349)
	at java.util.AbstractCollection.toArray(AbstractCollection.java:339)
	at java.util.ArrayList.<init>(ArrayList.java:97)

Collections.synchronizedList 并不能解决 ConcurrentModificationException 问题

查看 Collections.synchronizedList 源码可知底层返回的是 SynchronizedCollection 持有传入的list,只不过在 add、remove、get 等基础操作加了一个锁,但是 iterator 方法没有加锁,而且官方注释了 Must be manually synched by user! 需要使用者自己加锁处理。

1
2
3
public Iterator<E> iterator() {
            return c.iterator(); // Must be manually synched by user!
}

多线程读取同一个磁盘数据修改后复写到磁盘中。

在多线程场景下有可能