ArrayList线程不安全分析

        之前一直听说ArrayList线程不安全的说法,百度了一下不安全的分析和列子,感觉都不太满意,最后边实在不行只好自己分析了。

1、多线程插入测试

        代码实例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()方法的源码:

/**
     * 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),图解:

ArrayList线程不安全分析_第1张图片

        2)为什么list size为什么比Counter小

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

2、迭代器遍历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)。


你可能感兴趣的:(ArrayList,Fail-Fast,线程不安全)