集合并发不安全问题

解决集合并发不安全问题

1. List并发不安全

1、在并发环境下往list集合中添加或修改数据会出现ConcurrentModificationException异常(并发修改异常)

  1. 方法一:使用vector集合

    List<String> list = new Vector<>();
    
  2. 方法二:使用collections工具类

    List<E> list = Collections.synchronizedList(new ArrayList<>());
    
  3. 使用java.util.concurrent下的CopyOnWriteArrayList集合

    • 其底层也是一个数组实现。private transient volatile Object[] array; 由volatile修饰;
    • 它是一个线程安全的ArrayList;
    • 使用独占锁ReentrantLock来保证同时只有一个线程对array进行修改:final transient ReentrantLock lock = new ReentrantLock();
    //创建一个并发安全的list集合
    List<String>  list = new CopyOnWriteArrayList<>();
    
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }
    final void setArray(Object[] a) {
        array = a;  //array是不可序列化且由volatile修饰的
    }
    private transient volatile Object[] array;  //底层源码
    

2、CopyOnWrite :写入时复制 。简称COW 是一种优化策略。

  • 多线程使用list,写入时可能会存在覆盖操作。先复制一份,再给调用者。
  • 读写分离

3、如果让我们自己做一个写时复制的线程安全的list应该怎么做???需要考虑以下几点:

  1. 何时初始化list,初始化的list元素个数为多少,list是有限大小吗??
  2. 如何保证线程安全,比如多个线程进行读写时如何保证是线程安全的??
  3. 如何保证使用迭代器遍历list时的数据一致性??

解决上述三个问题:

//无参构造:创建一个大小为 0 的数组作为array的初始值
public CopyOnWriteArrayList() {
    setArray(new Object[0]);
}

//两个有参构造初始化数组
//1、创建一个list,其内部元素是入参toCopyIn的副本
public CopyOnWriteArrayList(E[] toCopyIn) {
    setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class));
}

//入参为一个集合,将集合里面的元素复制到 list集合中
public CopyOnWriteArrayList(Collection<? extends E> c) {
    Object[] elements;
    if (c.getClass() == CopyOnWriteArrayList.class)
        elements = ((CopyOnWriteArrayList<?>)c).getArray();
    else {
        elements = c.toArray();
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elements.getClass() != Object[].class)
            elements = Arrays.copyOf(elements, elements.length, Object[].class);
    }
    setArray(elements);
}

添加元素的实现:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;  //获取独占锁,如果多个线程都调用add方法则只有一个线程会获取到该锁,其他线程会被阻塞挂起直到锁被释放。保证线程安全
    lock.lock();
    try {
        Object[] elements = getArray(); //获取array
        //复制array到一个新数组(新数组是原数组的大小加 1, 所以CopyOnWriteArrayList是一个无界数组),并把新增的元素添加到新数组。
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        //使用新数组替换添加前的数组
        setArray(newElements);
        return true;
    } finally {
        //在返回前释放锁,先执行finally代码块,再执行return语句
        lock.unlock();
    }
}

总结

  • CopyOnWriteArrayList使用写时复制的策略来保证List的一致性。
  • 获取-修改-写入三步操作不是原子性的,所以在增删改的过程中都使用了独占锁,来保证在某个时间只有一个线程能对 list的数组进行修改
  • CopyOnWriteArrayList 提供了弱一致性的迭代器,即保证在获取迭代器后,其他线程对list的修改是不可见的(即修改是获取下一个迭代器时才生效)

2. Set并发不安全

方法一:使用collections工具类

 Set<String> set= Collections.synchronizedSet(new HashSet<>());

方法二:使用java.ytil.concurrent下的CopyOnWriteArraySet集合。

Set<String> set= new CopyOnWriteArraySet<>();

HashSet的底层就是一个HashMap。

public HashSet() {
    map = new HashMap<>();
}
// HashSet的add()方法的底层
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
private static final Object PRESENT = new Object(); //不变的值

3. Map并发不安全

Map<String, String> map = new HashMap<>();
  1. map是这样用的吗? 不是。

  2. 默认等价于什么? Map<String, String> map = new HashMap<>(16, 0.75);

  3. 加载因子(0.75),初始化容量(16)

    • 底层是写成位运算:1 < < 4 (初始化容量);
    • 加载因子:static final float DEFAULT_LOAD_FACTOR = 0.75f;
    • 最大容量: 1 << 30;
  4. 解决:使用JUC包下的ConcurrentHashMap集合

    import java.util.concurrent.ConcurrentHashMap;
    
    Map<String, String> map =new ConcurrentHashMap<>();
    
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值