一、ArrayList

先说结论,ArrayList是线程不安全的。至于为什么需要去了解它的实现原理,来看下它的源码。

首先ArrayList是基于数据实现的,分析它的线程安全问题需要看下add方法

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    //真正存储元素的数组
    transient Object[] elementData; // non-private to simplify nested class access
	//list当前的元素个数
    private int size;  
    
    //添加元素的方法
    public boolean add(E e) {
    	//这个方法确保elementData数组有足够的容量来存储元素e,也就是如果容量不够会进行扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把元素e放入数组size下标处并让size自增,
        //在多线程环境下++操作不是原子的,所以会产生线程安全问题,所以多个线程同时给size自增时
        //最后得到的size是不正确的且结果不确定,那下次执行add时要往size处放元素,放的位置也就是不确定的
        //假设3个线程同时放,3次++操作得到的size可能都是1,最后三个线程都往1处放元素,相当于丢了2个元素
        elementData[size++] = e;
        return true;
    }
    
    private void ensureCapacityInternal(int minCapacity) {
    	//先判断elementData是不是空,这个判断只有第一次add时才会进入
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        	// elementData是空需要计算出需要的最小容量,传进来的是size+1,DEFAULT_CAPACITY是10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
		
        ensureExplicitCapacity(minCapacity);
    }
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        //如果最小容量比当前数组长度大就需要进行扩容
        if (minCapacity - elementData.length > 0)
            //扩容方法
            grow(minCapacity);
    }
    
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        //新容量=旧容量+旧容量的一半
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //复制数组元素
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    //这个扩容的过程也是线程不安全的,假设线程t1计算出newCapacity后线程上下文切换了,其他线程又add了很多
    //元素这时容量可能已经变了,t1再恢复执行继续复制数组元素时就会出问题。
}

通过上边对源码的分析可以了解到ArrayList确实是线程不安全的。

二、线程安全的list实现

2.1 Vector

Vector 是一个线程安全的实现,它保证线程安全的方式就是每个方法都是用synchronized修饰的。

2.2 java.util.Collections#synchronizedList(java.util.List)

这个工具方法接收一个list,然后会对这个list进行包装,让每个方法都被synchronized修饰,

所以锁对象是当前List对象,读写不能同时进行。

2.3 CopyOnWriteArrayList

这个list实现内部有一个ReentrantLock,在写数据的时候先加锁,然后会把内部的数组复制一份进行写操作,通过这种方式保障线程安全,具体需要看下源码.

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    private static final long serialVersionUID = 8673264195747942595L;

    /** 锁对象 */
    final transient ReentrantLock lock = new ReentrantLock();
    
    //存元素的数组
    private transient volatile Object[] array;
    
    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        //添加元素时先加锁
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            //复制旧数组的元素到新数组,容量+1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            //把元素e添加到新数组中
            newElements[len] = e;
            //把新数组赋值给存元素的数组arr
            setArray(newElements);
            return true;
        } finally {
        	//释放锁
            lock.unlock();
        }
    }
    //获取元素的方法不加锁,存的同时可以进行获取,性能高
    public E get(int index) {
        return get(getArray(), index);
    }
}