一、使用Vector

Vector是线程安全的,我们可以看下Vector底层的方法是同步的(Synchronized修饰),从而可以解决ArrayList线程不安全的问题;

/**
 * Appends the specified element to the end of this Vector.
 *
 * @param e element to be appended to this Vector
 * @return {@code true} (as specified by {@link Collection#add})
 * @since 1.2
 */
public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);
    elementData[elementCount++] = e;
    return true;
}

二、使用Collections.synchronizedList()来解决ArrayList线程不安全的问题

将上述案例中创建list方法改成如下即可

List<Integer> list = Collections.synchronizedList(new ArrayList<>());
Collections.synchronizedList()方法中会这个根据传入的List是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList.

public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
            new SynchronizedRandomAccessList<>(list) :
            new SynchronizedList<>(list));
}

我们可以看下SynchronizedList中的方法也是同步的,使用了Synchronized修饰;
上面两种方法确实是可以解决ArrayList线程不安全的问题,但是两者都是将所有的方法都加锁,那会导致效率低下,只能一个线程操作完,下一个线程获取到锁才能操作;

我们来认识一下:CopyOnWriteArrayList

CopyOnWriteArrayList是如何保证线程安全的呢?
CopyOnWriteArrayList是一个线程安全的ArrayList,其实现原理是读写分离,其对写操作使用ReentrantLock来上锁,对读操作则不加锁;CopyOnWriteArrayList在写操作的时候,会将list中的数组拷贝一份副本,然后对其副本进行操作(如果此时其他线程需要读的事,那么其他线程读取的是原先的没有修改的数组,如果其他写操作的线程要进行写操作,需要等待正在写的线程操作完成,释放ReentrantLock后,去获取锁才能进行写操作),写操作完成后,会讲list中数组的地址引用指向修改后的新数组地址。

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    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;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

我们可以看到,如果使用场景的写操作十分频繁的话,建议还是不要实现CopyOnWriteArrayList,因为其添加的时候会造成数组的不断扩容和复制,十分消耗性能,会消耗内存,如果原数组的数据比较多的情况下,可能会导致young gc或者full gc;并且其不能使用在实时读的场景,在写操作过程中是要花费时间的,读取的时候可能还是旧数据;
CopyOnWriteArrayList 合适读多写少的场景, 如果我们在使用的时候没法保证CopyOnWriteArrayList 到底要放多少数据的话,我们还是要谨慎使用,如果数据稍微有点多,每次写操作都重新拷贝数组,其代价实在太高昂了。
————————————————

CopyOnWriteArraySet也是同理