Java CopyOnWriteArrayList 和 SynchronizedList 的区别

313 阅读2分钟

Java CopyOnWriteArrayList 和 SynchronizedList 的区别

  • java.util.concurrent.CopyOnWriteArrayList 和 java.util.Collections$SynchronizedList 两者都是 Java 中线程安全的 List 实现

CopyOnWriteArrayList

  • CopyOnWrite 代表写时复制,在执行写操作(如添加、删除或修改元素)时,会复制一份新数组进行相关的操作,然后在操作完成后将原来的集合指向新的集合
  • 由于读操作在原数组上进行,所以读操作不需要加锁,所以读操作比较高效,而写操作内存占用开销比较大,比较适合读多写少的情况
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    //...
    /**
     * The lock protecting all mutators.  (We have a mild preference
     * for builtin monitors over ReentrantLock when either will do.)
     */
    final transient Object lock = new Object(); //保证不会有多个线程同时复制一个新数组

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array; //volatile 关键字保证了变量的可见性

    /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
        return array;
    }

    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
        array = a;
    }

    /**
     * Creates an empty list.
     */
    public CopyOnWriteArrayList() {
        setArray(new Object[0]);
    }

    /**
     * Creates a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection of initially held elements
     * @throws NullPointerException if the specified collection is null
     */
    public CopyOnWriteArrayList(Collection<? extends E> c) {
        Object[] es;
        if (c.getClass() == CopyOnWriteArrayList.class)
            es = ((CopyOnWriteArrayList<?>)c).getArray();
        else {
            es = c.toArray();
            // Android-changed: Defend against c.toArray (incorrectly) not returning Object[]
            //                  (see b/204397945)
            // if (c.getClass() != java.util.ArrayList.class)
            if (es.getClass() != Object[].class)
                es = Arrays.copyOf(es, es.length, Object[].class);
        }
        setArray(es);
    }
    //...
    /**
     * Returns the number of elements in this list.
     *
     * @return the number of elements in this list
     */
    public int size() {
        return getArray().length;
    }
    
    public E get(int index) {
        return elementAt(getArray(), index);
    }

    /**
     * Replaces the element at the specified position in this list with the
     * specified element.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E set(int index, E element) {
        synchronized (lock) {
            Object[] es = getArray();
            E oldValue = elementAt(es, index);

            if (oldValue != element) {
                es = es.clone();
                es[index] = element;
            }
            // Ensure volatile write semantics even when oldvalue == element
            setArray(es);
            return oldValue;
        }
    }

    /**
     * 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) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            es = Arrays.copyOf(es, len + 1);
            es[len] = e;
            setArray(es);
            return true;
        }
    }

    /**
     * Removes the element at the specified position in this list.
     * Shifts any subsequent elements to the left (subtracts one from their
     * indices).  Returns the element that was removed from the list.
     *
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public E remove(int index) {
        synchronized (lock) {
            Object[] es = getArray();
            int len = es.length;
            E oldValue = elementAt(es, index);
            int numMoved = len - index - 1;
            Object[] newElements;
            if (numMoved == 0)
                newElements = Arrays.copyOf(es, len - 1);
            else {
                newElements = new Object[len - 1];
                System.arraycopy(es, 0, newElements, 0, index);
                System.arraycopy(es, index + 1, newElements, index,
                                 numMoved);
            }
            setArray(newElements);
            return oldValue;
        }
    }

    /**
     * Removes all of the elements from this list.
     * The list will be empty after this call returns.
     */
    public void clear() {
        synchronized (lock) {
            setArray(new Object[0]);
        }
    }

    /**
     * Returns an iterator over the elements in this list in proper sequence.
     *
     * <p>The returned iterator provides a snapshot of the state of the list
     * when the iterator was constructed. No synchronization is needed while
     * traversing the iterator. The iterator does <em>NOT</em> support the
     * {@code remove} method.
     *
     * @return an iterator over the elements in this list in proper sequence
     */
    public Iterator<E> iterator() {
        return new COWIterator<E>(getArray(), 0);
    }
    //...
}

Collections$SynchronizedList

  • 通过包装一个普通的 List 来实现,大部分的读写操作都用 synchronized 关键字(对象锁)来保证线程安全,而迭代方法没有加,所以在使用迭代方法的时候需要自行处理线程同步问题,由于读操作也被加锁了,所以性能上受到了一定影响
  • 常用方法 Collections.synchronizedList 返回一个 SynchronizedList 对象
  • SynchronizedList 比较适合需要简单地将普通非线程安全的 List 转换为线程安全的 List 的情况
//java.util.Collections
public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list));
}
//Collections 的静态内部类 SynchronizedList
static class SynchronizedList<E> extends SynchronizedCollection<E> implements List<E> {
    //...
    @SuppressWarnings("serial") // Conditionally serializable
    final List<E> list;
    SynchronizedList(List<E> list) {
        super(list);
        this.list = list;
    }
    //...
    public E get(int index) {
        synchronized (mutex) {return list.get(index);}
    }
    public E set(int index, E element) {
        synchronized (mutex) {return list.set(index, element);}
    }
    public void add(int index, E element) {
        synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
        synchronized (mutex) {return list.remove(index);}
    }
    //...
    public ListIterator<E> listIterator() {
        return list.listIterator(); // Must be manually synched by user
    }
    //...
}
//Collections 的静态内部类 SynchronizedCollection
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
    //...
    @SuppressWarnings("serial") // Conditionally serializable
    final Collection<E> c;  // Backing Collection
    @SuppressWarnings("serial") // Conditionally serializable
    final Object mutex;     // Object on which to synchronize   
    SynchronizedCollection(Collection<E> c) {
        this.c = Objects.requireNonNull(c);
        mutex = this;
    }
    //...
    public int size() {
        synchronized(this.mutex) {
            return this.c.size();
        }
    } 
    //...
    //使用 iterator 遍历的时候仍然需要手动加锁
    public Iterator<E> iterator() {
        return c.iterator(); // Must be manually synched by user!
    }   
    public boolean add(E e) {
        synchronized (mutex) {return c.add(e);}
    }
    public boolean remove(Object o) {
        synchronized (mutex) {return c.remove(o);}
    }
    //...
    public void clear() {
        synchronized (mutex) {c.clear();}
    }
    //...
}

使用

List<String> list = Collections.synchronizedList(new ArrayList<>());
//...
//需要自行处理 iterator 方法的线程同步问题
synchronized (list) {
    Iterator<String> iterator = list.iterator(); // Must be in synchronized block
    while (iterator.hasNext()) {
        String str = iterator.next();
        Log.e("TAG", "main: str=" + str);
    }
}

总结

  • 并发性能:由于 CopyOnWriteArrayList 读操作上没有加锁,所以 CopyOnWriteArrayList 读操作性能更好,而 SynchronizedList 由于读写操作都加了锁,所以读写都会受到一定影响

  • 内存占用:由于 CopyOnWriteArrayList 写操作会进行数组复制,所以 CopyOnWriteArrayList 在写操作上内存占用开销比 SynchronizedList 要大,所以不适合非常频繁写操作

  • 适用场景:CopyOnWriteArrayList 适用于读多写少的场景,而 SynchronizedList 则适用于读写操作相对均衡或者写操作相对较频繁的场景