ArrayList不允许写操作没执行完就执行读操作,正在读的时候不允许去写,必须写完再读,读完再写。

一:ArrayList不安全示例

使用ArrayList每次打印的集合数量可能会小于10000,而使用Vector每次都是10000

public class ListTest {
    public static void main(String[] args) throws InterruptedException {
        ThreadGroup group = new ThreadGroup("myGroup");
        List list = new ArrayList<>();
        // Vector list = new Vector<>();
        MyRunnable runnable = new MyRunnable(list);
        for (int i = 0; i < 10000; i++) {
            new Thread(group, runnable).start();
        }

        // 保证所有线程都执行完毕
        while (group.activeCount() > 0) {
            Thread.sleep(10);
        }

        System.out.println(list.size());
    }
}

class MyRunnable implements Runnable {
    private List list;
    public MyRunnable(List list) {
        this.list = list;
    }

    @Override
    public void run() {
        try {
            Thread.sleep((int)(Math.random() * 2));
        } catch (InterruptedException e) { }

        Random random = new Random();
        list.add(Thread.currentThread().getName() + "\t" + random.nextInt(100));
    }
}

ArrayList源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
   transient Object[] elementData;
   private int size;

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

    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        // 先赋值                 
        elementData[index] = element;
        // 再更新数量
        size++;
    }
}

通过源码可以看到ArrayList是通过一个Object[]数组来实现的,ArrayList在添加一个元素的时候,至少需要两步操作:1. 更新集合大小 2. 将元素添加到集合中, 即 elementData[size++] = e; (注意是size++, 而不是++size, 先使用值,然后再相加) add方法并没做什么同步加锁的处理,假如有两个线程,刚开始size=0; Thread-A线程将元素e添加到集合中,但并没有完成自加的操作,此时size仍然为0,elementData[0] = e,然后CPU切换到Thread-1线程,添加e2元素,因Thread-0并没有完成自加操作所以此时size仍然为0,Thread-1执行完所有的add操作,结果elementData[0] = e2; size=1,Thread-1执行完CPU切换到Thread-0,完成自增赋值操作,因Thread-1已经完成自层操作了,所以当时的size=1,Thread-0完成自增后size=2了,可以看到size为2,但是两个线程都是对索引0的位置操作,最终集合的长度为1,状态不一致。

elementData[size++] = e 并不是原子操作,实际上至少会被分解为4个原子操作

  1. 读取size的值
  2. int newSize = size + 1;
  3. size = newSize;
  4. elementData[size] = e;

ArrayList是线程不安全的,可以通过Collections包装成线程安全的集合

List<String> list = Collections.synchronizedList(new ArrayList<String>());

Collections: 集合工具类

package java.util;
public class Collections {
    // 将一些非线程安全的集合包装成线程安全的集合
    public static <T> Collection<T> synchronizedCollection(Collection<T> c);
    public static <T> Set<T> synchronizedSet(Set<T> s);
    public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s);
    public static <T> List<T> synchronizedList(List<T> list){
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }
    public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m);
    public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m);


    // SynchronizedList
    static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
        final List<E> list;

        SynchronizedList(List<E> list) {
            // 调用父类的构造函数
            super(list);
            this.list = list;
        }
        SynchronizedList(List<E> list, Object mutex) {
            super(list, mutex);
            this.list = list;
        }
     } 


    // SynchronizedCollection
    static class SynchronizedCollection<E> implements Collection<E>, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection<E> c;  // Backing Collection
        // 同步锁
        final Object mutex;     // Object on which to synchronize

        SynchronizedCollection(Collection<E> c) {
            this.c = Objects.requireNonNull(c);
            // 同步锁为this,当前对象
            mutex = this;
        }

        public boolean add(E e) {
            // add 方法使用了同步锁: this
            // 同一时间只能添加一个
            synchronized (mutex) {return c.add(e);}
        }

    }  
}

示例一

public class WriteThread extends Thread {
    private final List<Integer> list;
    public WriteThread(List<Integer> list) {
        super("WriteThread");
        this.list = list;
    }

    @Override
    public void run() {
        for (int i = 0; true; i++) {
            list.add(i);
        }
    }
}
public class ReaderThread extends Thread {
    private final List<Integer> list;
    public ReaderThread(List<Integer> list) {
        super("ReaderThread");
        this.list = list;
    }

    @Override
    public void run() {
        while (true) {
            for (int n : list) {
                System.out.println(n);
            }
        }
    }
}
public class Main {

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        new WriteThread(list).start();
        new ReaderThread(list).start();
    }
}
Exception in thread "ReaderThread" java.util.ConcurrentModificationException
    at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901)
    at java.util.ArrayList$Itr.next(ArrayList.java:851)
    at com.company.example.chapter02.example2.ReaderThread.run(ReaderThread.java:25)

示例2

public static void main(String[] args) {
    // 包装成同步集合
    final List<Integer> list = Collections.synchronizedList(new ArrayList<>());
    // SynchronizedCollection#add 已经加了同步锁this,所以不会发生同步写
    new WriteThread(list).start();
    // 虽然写与写不会同步,但是目前写的同时可以读,这样是不行的,必须保证写的时候不能读,读的时候不能写
    // 所以在读的时候也要加锁,而且读的时候加的锁必须和写的锁一致
    // 如果能保证读与写互斥,写与写互斥就没有线程安全问题
    // 注意:虽然Collections.synchronizedList是线程安全的,但是并不是说你的程序也安全,同时也要保证自己的逻辑也要安全
    new ReaderThread(list).start();
}

public class ReaderThread extends Thread {
    // ...

    @Override
    public void run() {
        while (true) {
            // 读写互斥
            synchronized (list) {
                for (int n : list) {
                    System.out.println(n);
                }
            }
        }
    }
}

示例三
CopyOnWriteArrayList是线程安全的,copy-on-write:写时复制,当对集合执行写操作是,内部已确保安全的数组就会被整体复制,复制之后,就无需在使用迭代器依次读取元素时担心元素被修改了,所以在读时也不会发生java.util.ConcurrentModificationException

public static void main(String[] args) {
    // WriteThread 和 ReaderThread 和示例一完全一样
    // 只需要使用CopyOnWriteArrayList
    final List<Integer> list = new CopyOnWriteArrayList<>();

    new WriteThread(list).start();
    new ReaderThread(list).start();
}

CopyOnWriteArrayList

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    // 锁
    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;
            // 拷贝一个新的数组
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 将要添加的元素添加到新数组中
            newElements[len] = e;
            // 将新数组整体复制给array
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }


    final Object[] getArray() {
        return array;
    }

    final void setArray(Object[] a) {
        array = a;
    }


    // get无需加锁
    public E get(int index) {
        return get(getArray(), index);
    }   
}

使用copy-on-write时,每次执行写操作都会执行复制。因此频繁执行写操作时,如果使用CopyOnWriteArrayList会比较花费时间,如果写操作较少读操作较多就非常适用该集合。


Vector

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {

    protected Object[] elementData;
    protected int elementCount;

    // 线程安全,使用synchronized
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
   }
}

将上面的list声明为Vector可以看到每次执行集合的长度都是10000

三:Vector与ArrayList比较

Vector与ArrayList主要在于线程安全和性能上的区别。

  • Vector与ArrayList本质上都是一个Object[] 数组,ArrayList提供了size属性,Vector提供了elementCount属性,他们的作用是记录集合内有效元素的个数。
  • Vector是线程安全的集合类,ArrayList并不是线程安全的类。Vector类对集合的元素操作时都加了synchronized,保证线程安全。
  • Vector与ArrayList的扩容并不一样,Vector默认扩容是增长一倍的容量,Arraylist是增长50%的容量。
  • Vector与ArrayList的remove,add(index,obj)方法都会导致内部数组进行数据拷贝的操作,这样在大数据量时,可能会影响效率。