ArrayList集合解决线程不安全问题

本文深入探讨了ArrayList在多线程环境下并发修改异常的原因,分析了其内部机制,并提供了几种解决方案,包括使用Vector、CopyOnWriteArrayList及Collections.synchronizedList。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ArrayList

ArrayList是我们在开发中经常使用的一种集合,具有效率高并发性能强的特点。但是它是线程不安全的,会在高并发的情况下发生异常。

模拟线程不安全情况

 public static void main(String[] args) {
        List<String> list= new ArrayList<>();
        //创建10个线程同时对ArrayList集合进行读和写操作
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,6));
                System.out.println(list);},String.valueOf((i))).start();
        }
    }

会报出java.util.ConcurrentModificationException并发修改异常。

导致原因

1.at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)

//ArrayList发生异常的源代码
 final void checkForComodification() {
 //modCount实际修改的值,expectedModCount期望值
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

每次的调用add方法之后modCount的值都会发生改变,而expectedModCount期望值应该与实际操作值modCount相等,如果不相等就会抛出ConcurrentModificationException()并发修改异常。
2.at java.util.ArrayList$Itr.next(ArrayList.java:851)

//发生异常部分源代码
 public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

3.at java.util.AbstractCollection.toString(AbstractCollection.java:461)

//ArrayList发生异常的源代码
 public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

toString在迭代的过程中读写不一致。
总结
1.ArrayList在add()方法中没有加synchronized同步锁。

//ArrayList添加方法源代码
 public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

2.toString方法读写不一致(期望值与实际操作值不一致)

解决方法

  1. Vector集合(不建议)线程安全的,因为加了synchronized同步锁。
  2. 使用集合工具类Collections.synchronizedList();
  3. CopyOnWriteArrayList<>()写时复制集合。

CopyOnWriteArrayList 写时复制

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

CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将当前容器Object[]进行Copy, 复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加完元素之后, 再将原容器的引用指向新的容器 setArray(newElements);。这样做的好处是可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值