JUC-集合线程安全

List的线程安全

先来看这么一段代码,我们创建了一个ArrayList,在循环中通过两个线程对这个list进行并发修改

package com.lchtest.juc;
import java.util.*;
import java.util.concurrent.CopyOnWriteArraySet;

public class CopyOnWriteArraySetTest {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 1; i <= 10; i++){
            int temp = i;
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,6));
                System.out.println(list);
            },String.valueOf(temp)).start();

            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,6));
                System.out.println(list);
            },String.valueOf(temp)).start();
        }
    }
}

执行结果如下,报了一个ConcurrentModificationException:
在这里插入图片描述
为什么会产生并发修改异常?因为arraylist的源码中,add方法没有加同步锁

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

那么怎么才能正确的对arraylist并发修改呢?只需要把ArrayList换成CopyOnWriteArrayList即可: List list = new CopyOnWriteArrayList<>(); 那么它是如何实现的,来看源码

    /**
     * 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();  // 获取list中原来的元素
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);  // 新创建一个数组,保存原有的和新增的元素
            newElements[len] = e;   // 新数组中添加新元素
            setArray(newElements);  // 将数组引用指向新数组
            return true;
        } finally {
            lock.unlock();
        }
    }

CopyOnWriteArrayList是线程安全的 ArrayList,它的add操作进行了加锁,同一时刻只能有一个线程对list进行添加操作,和 ArrayList 一样,它是个可变数组,不同点在于,它采用了写时复制的思想,当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据,那么CopyOnWriteArrayList是怎么解决这个问题的? 来看它的setArray方法:

private transient volatile Object[] array;
final void setArray(Object[] a) {
        array = a;
}

这个array是加了volatile关键字的,volatile保证了线程对共享变量操作的结果对其他线程可见,一个线程读取 volatile 数组时,总能看
到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
CopyOnWriteArrayList 缺点:
由于CopyOnWriteArrayList 在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。

解决ArrayList并发修改问题:使用CopyOnWriteArrayList
解决HashSet并发修改问题: 使用CopyOnWriteArraySet
解决hashmap线程不安全问题:ConcurrentHashMap

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值