ArrayList是否线程安全?如何线程安全地操作ArrayList?

139 阅读3分钟

1 ArrayList是否线程安全?如何线程安全地操作ArrayList?

1.1 非线程安全

ArrayList是一个有序集合,底层是基于数据实现的。所以查询速度快,但是增删相对较慢。而且是一个非线程安全的集合。有多种原因会产生异常,本文只针对add()与remove()方法做阐述。 下述add()方法源码:

public boolean add(E e) {
    // 1.判断列表的capacity容量是否足够,是否需要扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    // 真正将元素放在列表的元素数组里面
    elementData[size++] = e; 
    return true; 
}

add元素时实际上时做了两个步骤

  1. 判断elementData数组容量是否满足需求,是否需要扩容
  2. 在elementData对应的位置上设置值

下述remove()方法源码:

public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

由上述源码可知,假如有多个线程同时执行add()方法或者同时存在新增与删除操作,有可能会报出一个数组越界的异常ArrayIndexOutOfBoundsException。

1.2 解决方案:

1.2.1 使用Vector

Vector是一个线程安全的列表,底层采用数组实现。其线程安全的实现方式非常粗暴:Vector大部分方法和ArrayList都是相同的,只是加上synchronized关键字,这种实现方式严重影响效率。以add()方法为例:

public synchronized boolean add(E e) { 
    modCount++; 
    ensureCapacityHelper(elementCount + 1); 
    elementData[elementCount++] = e; 
    return true; 
}

1.2.2 使用 Collections里面的synchronizedList

源码如下:

static class SynchronizedList<E>
        extends SynchronizedCollection<E>
        implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;

    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;
    }

    public boolean equals(Object o) {
        if (this == o)
            return true;
        synchronized (mutex) {return list.equals(o);}
    }
    public int hashCode() {
        synchronized (mutex) {return list.hashCode();}
    }

    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 int indexOf(Object o) {
        synchronized (mutex) {return list.indexOf(o);}
    }
    public int lastIndexOf(Object o) {
        synchronized (mutex) {return list.lastIndexOf(o);}
    }

    public boolean addAll(int index, Collection<? extends E> c) {
        synchronized (mutex) {return list.addAll(index, c);}
    }

    public ListIterator<E> listIterator() {
        return list.listIterator(); // Must be manually synched by user
    }

    public ListIterator<E> listIterator(int index) {
        return list.listIterator(index); // Must be manually synched by user
    }

    public List<E> subList(int fromIndex, int toIndex) {
        synchronized (mutex) {
            return new SynchronizedList<>(list.subList(fromIndex, toIndex),
                                        mutex);
        }
    }

    @Override
    public void replaceAll(UnaryOperator<E> operator) {
        synchronized (mutex) {list.replaceAll(operator);}
    }
    @Override
    public void sort(Comparator<? super E> c) {
        synchronized (mutex) {list.sort(c);}
    }

    /**
     * SynchronizedRandomAccessList instances are serialized as
     * SynchronizedList instances to allow them to be deserialized
     * in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
     * This method inverts the transformation.  As a beneficial
     * side-effect, it also grafts the RandomAccess marker onto
     * SynchronizedList instances that were serialized in pre-1.4 JREs.
     *
     * Note: Unfortunately, SynchronizedRandomAccessList instances
     * serialized in 1.4.1 and deserialized in 1.4 will become
     * SynchronizedList instances, as this method was missing in 1.4.
     */
    private Object readResolve() {
        return (list instanceof RandomAccess
                ? new SynchronizedRandomAccessList<>(list)
                : this);
    }
}

由源码可知,synchronizedList方法除listIterator()方法外,都使用了synchronized关键词修饰。因此如果需要迭代操作必须在外面加一层锁。如下:

List list = Collections.synchronizedList(new ArrayList());
  ...
  synchronized (list) {
  Iterator i = list.iterator(); // Must be in synchronized block
  while (i.hasNext())
      foo(i.next());
}

1.2.3 使用CopyOnWriteArrayList

CopyOnWrite(简称:COW):即复制再写入,jdk1.5后加入的,以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(); 
    } 
 }

1.2.4 使用显式锁

自己在业务逻辑上使用 java.util.concurrent.locks 包中提供的显式锁(如 ReentrantLock)来手动实现对 ArrayList 的同步。以add操作为例,代码示例如下:

public void add(E e) { 
    ReentrantLock lock = new ReentrantLock();
    lock.lock(); 
    try { 
        // 在锁内部调用 ArrayList 的 add 方法 
        arrayList.add(e); 
    }finally { 
        lock.unlock(); 
    } 
}

1.2.5 自己实现一个MyArrayList继承自ArrayList

自己实现一个继承自ArrayList的类,按照自己的需求给相应的方法上添加synchronized关键字修饰,并调用ArrayList的方法。以add()方法为例,示例代码如下:

public class MyArrayList<E> extends ArrayList<E> {
    @Override 
    public synchronized boolean add(E e) { 
        // 在 synchronized 方法中调用 ArrayList 的 add 方法 
        return super.add(e); 
    }
}