多线程并发为什么不能用普通的List集合详解

108 阅读2分钟

list集合add方法源码如下 其他源码自己翻阅

public boolean add(E e) {

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

     elementData[size++] = e;

    return true;

}

分为两种情况:

第一 读写不分离****

原因:执行size++,将此list的大小增加。然后再将传进的元素赋值给新增的内存空间elementData[size++]。

但是此时如果在size++后未完成赋值时直接来取这个List,而没有让add完成赋值操作,则会导致此List的长度加一,但是最后一个元素是空(null),所以导致在获取它进行后面处理数据的时候空指针。

第二 读写已经分离但是并发写也会有问题,也有两种情况****

当我们使用线程不安全list时,虽然已经进行了线程等待,进行了读写分离,但是线程等待不会影响顺序,在一个线程写的过程中另一个线程仍然可以继续写,此时就会出现并发写的问题

第一种情况

当A线程进行add时,首先进行了size++,但是还未进行赋值,此时B线程也进行add,也进行了size++,此时会导致elementData的引用发生改变 ,A线程创建的elementData地址空间此时未赋值,一直为null,线程A添加的元素会添加到B线程创建的elementData地址空间中,且会被线程B添加的元素进行覆盖,list倒数第二位出现null值导致后续使用出现空指针。

第二种情况

主线程创建不安全集合,线程A操作此集合时线程B也对其进行操作,并且导致了集合扩容,此时线程A操作的集合的引用会发生改变指向新集合,并且扩容的空间会为null,线程A这时进行操作时会有几率导致空指针。

解决方案:

1. 使用并发安全集合CopyOnWriteArrayList,此集合在 写(除了读)时会加锁复制,修改数组之前先将数组拷贝一份,操作新数组,并赋值给array,旧数组丢弃保证线程安全,读操作无锁读的是旧数组,写不会阻塞读,读写分离

2. 使用Future,Future是一个未来对象,里面保存这线程处理结果

用Future的list集合存放线程操作返回的list结果,后续使用就遍历Future的list集合就可以。Future中的结果(Future.get()方法),Future.get()读取时会阻塞,会等结果返回完毕再读取

总结:此处可以改为使用CopyOnWriteArrayList安全集合进行处理,但是仍需像之前代码一样进行线程等待,等待操作结束后在进行获取。而使用furure进行结果获取,不用在进行手动的等待,Future.get()读取时的阻塞机制可以满足要求。