美文网首页
ArrayList线程不安全举例及解决

ArrayList线程不安全举例及解决

作者: 陈二狗想吃肉 | 来源:发表于2021-05-19 15:48 被阅读0次

ArrayList部分源码

public class ArrayList<E> extends AbstractList<E>

        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

{

    /**

    * 列表元素集合数组

    * 如果新建ArrayList对象时没有指定大小,那么会将EMPTY_ELEMENTDATA赋值给elementData,

    * 并在第一次添加元素时,将列表容量设置为DEFAULT_CAPACITY

    */

    transient Object[] elementData;

    /**

    * 列表大小,elementData中存储的元素个数

    */

    private int size;

}

add方法

public boolean add(E e) {

    /**

    * 添加一个元素时,做了如下两步操作

    * 1.判断列表的capacity容量是否足够,是否需要扩容

    * 2.真正将元素放在列表的元素数组里面

    */

    ensureCapacityInternal(size + 1);  // Increments modCount!!

    elementData[size++] = e;

    return true;

}

ArrayList线程不安全的表现

在多个线程进行add操作时可能会导致elementData数组越界。

public static void main(String[] args) throws InterruptedException {

        final List<Integer> list = new ArrayList<Integer>();

        // 线程A将0-1000添加到list

        new Thread(() -> {

            for (int i = 0; i < 1000 ; i++) {

                list.add(i);

                try {

                    Thread.sleep(1);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }).start();

        // 线程B将1000-2000添加到列表

        new Thread(() -> {

            for (int i = 1000; i < 2000 ; i++) {

                list.add(i);

                try {

                    Thread.sleep(1);

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }

            }

        }).start();

        Thread.sleep(1000);

        // 打印所有结果

        for (int i = 0; i < list.size(); i++) {

            System.out.println("第" + (i + 1) + "个元素为:" + list.get(i));

        }

}

多线程情况下,一个线程正在写入,另一个线程也在写入,导致数据不一致异常,并发生修改异常 java.util.ConcurrentModificationException

public static void main(String[] args) throws InterruptedException {

        List<String> list = new ArrayList<>();

        for (int i =1; i<=30 ; i++) {

            new Thread(() -> {

                list.add("a");

                list.add("b");

                list.add("c");

                list.add("d");

                System.out.println(list.toString());

            }).start();

        }

}

解决办法

//Synchronized对代码进行加锁,力度大,所以代码执行效率低下

List<String> list = Collections.synchronizedList(new ArrayList<String>());

//写时复制通过lock机制进行枷锁

/*CopyOnWrite容器即写时复制的容器。往一个容器添加元索的时候,不直接往当前容器Object[]添加,

而是先将当前容器Object[]进行Copy,复制出一个新的容器object[] newElements,

然后往新的容器object[] newElements 里添加元素,

添加完元素之后,再将原容器的引用指向新的容器setArray(newElements);。

这样做的好处是可以CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。

所以CopyOnWrite 容器也是一种读写分离的思想,读和写不同的容器*/

List<String> list = new CopyOnWriteArrayList();

List<String> list = new Vector<>();

CopyOnWriteArrayList add方法源码

/**

* Appends the specified element to the end of this list.

*

* @param e element to be appended to this list

* @return {@code true} (as specified by {@link Collection#add})

*/

public boolean add(E e) {

    final ReentrantLock lock = this.lock;

    lock.lock();

    try {

        Object[] elements = getArray();

        int len = elements.length;

        Object[] newElements = Arrays.copyOf(elements, len + 1);

        newElements[len] = e;

        setArray(newElements);

        return true;

    } finally {

        lock.unlock();

    }

}

额外说下 ArrayList与LinkedList;这两个都是接口List下的一个实现,用法都一样,但用的场所的有点不同,ArrayList适合于进行大量的随机访问的情况下使用,LinkedList适合在表中进行插入、删除时使用,二者都是非线程安全,解决方法同上(为了避免线程安全,以上采取的方法,特别是第二种,其实是非常损耗性能的)

————————————————

相关文章

网友评论

      本文标题:ArrayList线程不安全举例及解决

      本文链接:https://www.haomeiwen.com/subject/yutpjltx.html