ArrayList线程不安全分析

        以前一直据说ArrayList线程不安全的说法,百度了一下不安全的分析和列子,感受都不太满意,最后边实在不行只好本身分析了。 java

一、多线程插入测试

        代码实例ArrayListTestDemo1.java: 安全

package com.hjr.back16.common.util;

import static java.lang.System.*;
import java.util.ArrayList;
import java.util.List;

/**
 * 测试ArrayList线程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo1 {
	public static Counter counter = new Counter();
	
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
//		for (int i = 0; i < 10000; i++) {
//			list.add(i);
//		}
		
		for (int i = 0; i < 10; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 1000; j++) {
						list.add(1000*j + j); // 结果中抛出异常的代码
						counter.increment();
					}
				}
			}.start();
		}
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		out.println("list size: " + list.size());
		out.println("Counter: " + counter.getValue());
	}
}

class Counter {
	
	private int value;

	public synchronized int getValue() {
		return value;
	}

	public synchronized int increment() {
		return ++value;
	}

	public synchronized int decrement() {
		return --value;
	}
}
        Demo执行结果:
Exception in thread "Thread-5" Exception in thread "Thread-7" Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: 49
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: 51
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 54
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
java.lang.ArrayIndexOutOfBoundsException: 50
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
Exception in thread "Thread-2" java.lang.ArrayIndexOutOfBoundsException: 52
	at java.util.ArrayList.add(ArrayList.java:441)
	at com.hjr.back16.common.util.ArrayListTestDemo1$1.run(ArrayListTestDemo1.java:25)
list size: 4858
Counter: 5050
        分析执行结果的时候,咱们可能会发现两个问题:1)为何抛出异常;2)为何list size为何比Counter小?

        1)为何抛出异常 多线程

        咱们先看下ArrayList.add()方法的源码: app

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
        上边源码中,ensureCapacityInternal方法主要是判断须要的容量(size+1)是否大于 ArrayList的容量(elementData.length),若是大于 根据规则增长ArrayList的容量,不然 直接返回

        因为ArrayList.add()没有同步锁,因此多线程调用这个方法的时候,有可能多个线程拿到相同的size值去与ArrayList的容量作比较,而执行到elemntData[size++] = e;时倒是有序的,这时,因为ensureCapacityInternal()没有适当的扩大ArrayList的容量,从而致使插入数据的长度大于ArrayList的剩余容量,因而也就抛出了越界的异常(java.lang.ArrayIndexOutOfBoundsException),图解: 测试


        2)为何list size为何比Counter小 this

        对于这个问题,须要说明的是如代码所示,Counter.increment()方法是加了同步锁的,因此Counter输出的值就是list.add()成功的次数。然而,list size比Counter小,一样是由于多线程进入add方法时,拿到相同的size执行elementData[size++] = e;这句代码,而Java的自增操做又不是原子性操做,这样就致使了多个线程拿相同的size执行加1的操做,最后边就出现了list size小于Counter,也就小于世界成功执行add()方法次数的问题。 spa

二、迭代器遍历List时作修改

        代码实例ArrayListTestDemo2.java: 线程

package com.hjr.back16.common.util;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 测试ArrayList线程不安全的例子
 * @author scuechjr
 * @date 2016-4-24 1:29:48
 */
public class ArrayListTestDemo2 {
	public static void main(String[] args) {
		final List<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < 10000; i++) {
			list.add(i);
		}
		
		// 列表遍历线程
		new Thread() {
			public void run() {
				Iterator<Integer> iterator = list.iterator();
				while(iterator.hasNext()) {
					iterator.next(); // 异常抛出的地方
				}
			}
		}.start();
		
		// 列表新增元素线程
		new Thread() {
			public void run() {
				for (int j = 0; j < 1000; j++) {
					list.add(1000*j + j);
				}
			}
		}.start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
        Demo执行结果:
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:859)
	at java.util.ArrayList$Itr.next(ArrayList.java:831)
	at com.hjr.back16.common.util.ArrayListTestDemo2$1.run(ArrayListTestDemo2.java:25)
        异常来源代码片断ArrayList内部类Itr:
/**
     * An optimized version of AbstractList.Itr
     */
    private class Itr implements Iterator<E> {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        ... ...

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }
        根据异常信息,咱们知道异常是Itr.checkForComodification()这个方法抛出的 。其中,modCount是一个ArrayList用于记录ArrayList修改(包括列表元素的增删改等)次数的成员变量;expectedModCount是初始话一个新的Iterator时的modCount值。整个方法主要用于判断ArrayList在Iterator遍历的过程当中,是否发生修改,若是发生修改expectedModCount与modCount不一样),抛出异常(这实际上是Java集合的一种错误机制:fail-fast)。
相关文章
相关标签/搜索