ArrayList为什么会出现并发问题以及相应的解决办法

您所在的位置:网站首页 java多线程容易出现的问题有哪些呢 ArrayList为什么会出现并发问题以及相应的解决办法

ArrayList为什么会出现并发问题以及相应的解决办法

2024-07-14 01:02| 来源: 网络整理| 查看: 265

问题一:ArrayList为什么会出现并发问题?

ArrayList是线程不安全的,在多线程并发访问的时候可能会出现问题,如果想使用线程安全的集合类,java自带有vector,也就是说vector是线程安全的。但是arayList的底层是数组实现的,而且可以自动扩容,获得元素或者在数组尾段插入元素的效率高,所以说ArrayList有其独特的优势。

1.扩容实现

private transient Object[] elementData; public void ensureCapacity(int minCapacity) { modCount++; int oldCapacity = elementData.length; if (minCapacity > oldCapacity) { Object oldData[] = elementData; int newCapacity = (oldCapacity * 3)/2 + 1; if (newCapacity < minCapacity) newCapacity = minCapacity; // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); } }

elementData数组的初始容量是10,如果需要扩容时,使用 elementData = Arrays.copyOf(elementData, newCapacity)进行复制, elementData = Arrays.copyOf(elementData, newCapacity)底层的核心代码为:

System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength)); 该方法中,original指的是:elementData,旧数组,0指的是复制从elementDate的索引为0开始 copy新生成的数组,0,新数组的索引为0开始,最后一个参数Math.min(elementData.length,newCapacity) 在上面的源码中,它的意思就是把老数组中的数据复制到一个新生成的容量为newLength的数组中。

扩容的时候,如果在并发操作ArrayList的时候,可能会有数组索引越界的异常产生。

分析上源码可以看出,只有当minCapacipityoldCapacipity时才发生扩容问题,假设minCapacipity=oldCapacipity=10,不发生扩容问题,元素是可以被插入的。大家可以看下面的add源码,也就是size=9时可以将元素添加到数组中,多线程示意图如下:

线程A和线程B获取的size都是9,线程A先插入元素e,这个时候elementData数组的大小为10,是正常情况下下次应该是要扩容的,但是线程B获取的size=9而不是10,在线程B中没有进行扩容,而是报出数组index越界异常。

2.add操作

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

add操作中先进行扩容操作ensureCapacity(size+1),之后才添加数据到elementData这个数组的末端,但是这样的操作不是线程安全的,多线程操作的时候可能会出现数据覆盖的问题。

线程A执行了ArrayList的add方法,由于线程B获取到的size大小和线程A是一样的,此时的size大小应该是比原来的size要大1,但是B线程不知,所以B线程进行赋值的时候把A线程的值给覆盖,导致添加到数组中元素的个数其实是比逻辑上要少的。

package TestArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main { private static List list = new ArrayList(); private static ExecutorService executorService = Executors.newFixedThreadPool(1000); //定长线程池1000 private static class IncreaseTask extends Thread{ @Override public void run() { System.out.println("ThreadId:" + Thread.currentThread().getId() + " start!"); for(int i =0; i < 100; i++){ list.add(i); } System.out.println("ThreadId:" + Thread.currentThread().getId() + " finished!"); } } public static void main(String[] args){ for(int i=0; i < 1000; i++){ //开启1000个线程 executorService.submit(new IncreaseTask()); } executorService.shutdown(); while (!executorService.isTerminated()){ try { Thread.sleep(1000*10); }catch (InterruptedException e){ e.printStackTrace(); } } System.out.println("All task finished!"); System.out.println("list size is :" + list.size()); } }

结果:list某个随机值是99900,本应该是100000,所以add操作是线程不安全的。

问题二:如何避免ArrayList的并发问题?

(1)使用Collections.synchronizedList()方法对ArrayList对象进行包装

ArrayList arraylist = Collections.synchronizedList(new ArrayList());

将问题一中的Arraylist改成Collections.synchronizedList(new ArrayList)生成,问题的结果是100000.

源码:

public static List synchronizedList(List list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList(list) : new SynchronizedList(list)); }

上述类的继承结构如上所示,SynchronizedList是SynchronizedRandomAccessList的父类,我们现在看下SynchronizedList的源码,了解下为什么SynchronizedList是线程安全的。

SynchronizedList(List list) { super(list); this.list = list; } SynchronizedList(List list, Object mutex) { super(list, mutex); this.list = list; } public boolean equals(Object o) { synchronized(mutex) {return list.equals(o);} } public int hashCode() { synchronized(mutex) {return list.hashCode();} } public E get(int index) { synchronized(mutex) {return list.get(index);} } public E set(int index, E element) { synchronized(mutex) {return list.set(index, element);} } public void add(int index, E element) { synchronized(mutex) {list.add(index, element);} } public E remove(int index) { synchronized(mutex) {return list.remove(index);} } public int indexOf(Object o) { synchronized(mutex) {return list.indexOf(o);} } public int lastIndexOf(Object o) { synchronized(mutex) {return list.lastIndexOf(o);} } public boolean addAll(int index, Collection


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3