ArrayList为什么会出现并发问题以及相应的解决办法 |
您所在的位置:网站首页 › java多线程容易出现的问题有哪些呢 › ArrayList为什么会出现并发问题以及相应的解决办法 |
问题一: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的时候,可能会有数组索引越界的异常产生。 分析上源码可以看出,只有当 线程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线程的值给覆盖,导致添加到数组中元素的个数其实是比逻辑上要少的。 结果: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 |