1.ArrayList简介[1]
ArrayList实现了List接口。ArrayList的方法实现和vector相似,只是线程不安全的。
ArrayList的 size、isEmpty、get、set、iterator等方法的时间复杂度为O(1),add n个元素需要O(n)的时间(add一个元素的时间摊销成本是O(1)),其它方法的运行时间多为线性的。比如remove(int index)的时间复杂度是O(n);remove(Object obj)的时间复杂度是O(n),removeAll的时间复杂度是O(n)。
ArrayList的初始容量是10,扩容机制是oldCapacity+oldCapacity>>1,add一个元素的时间摊销成本是常量。
ArrayList是线程不安全的。当多个线程同时操作ArrayList时,且至少一个线程对ArrayList进行结构性修改时,可能出现线程不安全。需要采取一些策略以确保线程安全,具体详见第3节。
使用iterator()或listIterator(int index)获取的迭代器,是fail-fast的;如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),再调用迭代器的方法将会抛出异常。这避免了在将来某个不确定的时间出现线程安全问题。
2.使用ArrayList的注意事项[2]
1)如果元素是引用类型,使用remove/removeAll,contains/retainAll前,需要重写元素的equals()方法,重写equals前,需要重写hashcode方法。因为这些方法的底层都调用了equals方法。
2)不要在for(Element e:list)循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁(如果使用的List类未加锁,需要开发者自己加锁)。
在for(Element e:list)里进行元素的add/remove/clear操作时会报错,如图1。报错的的原因是for(Element e:list)的底层是迭代器(如图2)。使用迭代器时,如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),会有fail-fast的机制,如图3。迭代器的fail-fast是指在使用迭代器遍历时,如果使用了迭代器类之外的方法对list进行结构性修改(remove/add/clear等),则使用迭代器的遍历将终止,并抛出异常ConcurrentModificationException。
public class ArrayListStudy1 {
//forEach中使用add/remove方法
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
for(Integer i:list){
if(1==i){
list.remove(1);
}
}
}
}
运行报错:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
at java.util.ArrayList$Itr.next(ArrayList.java:859)
at com.study.collection.ArrayListStudy2.main(ArrayListStudy2.java:15)
图1 在for(Element e:list)里进行元素的add/remove/clear操作
反编译得到以下代码:
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
Integer i = it.next();
if (1 == i.intValue()) {
list.remove(1);
}
}
图2 图1代码反编译后的代码(反编译工具jadx-gui-1.3.3-1)
查看迭代器的源码:
/**
* Returns an iterator over the elements in this list in proper sequence.
*
* <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>.
*
* @return an iterator over the elements in this list in proper sequence
*/
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
//如果使用了Iterator类之外的方法对list进行结构性修改(remove/add/clear等),则抛出异常(fail-fast)
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
ArrayList类注释中对fail-fast的详细注释:
/**
* <p><a name="fail-fast">
* The iterators returned by this class's {@link #iterator() iterator} and
* {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a>
* if the list is structurally modified at any time after the iterator is
* created, in any way except through the iterator's own
* {@link ListIterator#remove() remove} or
* {@link ListIterator#add(Object) add} methods, the iterator will throw a
* {@link ConcurrentModificationException}. Thus, in the face of
* concurrent modification, the iterator fails quickly and cleanly, rather
* than risking arbitrary, non-deterministic behavior at an undetermined
* time in the future.
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
}
3)SubList是ArrayList的视图,它拥有ArrayList相同的方法;但SubList不是ArrayList类或子类,它是ArrayList的内部类,不能将SubList强转为ArrayList类型。如果将SubList强转为ArrayList,则会报错,如图3。所有对ArrayList的非结构性(非add/remove/clear)的操作,subList也会相应变化,反之亦然。如果使用了SubList类之外的方法对list进行结构性修改(remove/add/clear等),对subList的所有操作都会报错。这类似于第二条中的fail-fast机制。
如果使用了SubList类之外的方法对list进行结构性修改(remove/add/clear等),对subList的所有操作都会报错。subList的常用方法如图4所示。在这些方法中都有 checkForComodification()方法,在单线程中,这个方法会判断ArrayList是否有通过了SubList类之外的方法对list进行结构性修改(remove/add/clear等),如果有,则会抛出异常ConcurrentModificationException。
public class ArrayListStudy2 {
public static void main(String[] args) {
CopyOnWriteArrayList<Integer> list = new CopyOnWriteArrayList<>();
list.add(1);
list.add(2);
list.add(3);
ArrayList list = (ArrayList) list.subList(0, 3);
}
}
运行报错:
Exception in thread "main" java.lang.ClassCastException: java.util.concurrent.CopyOnWriteArrayList$COWSubList cannot be cast to java.util.ArrayList
at com.study.collection.ArrayListStudy2.main(ArrayListStudy2.java:17)
图3 SubList强转为ArrayList运行报错
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private class SubList extends AbstractList<E> implements RandomAccess {
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
private void checkForComodification() {
if (ArrayList.this.modCount != this.modCount)
throw new ConcurrentModificationException();
}
}
}
图4 SubList的常用方法
4)toArray()方法后,进行类型转换会报错,如图5。这是因为toArray()得到的数组是Object[]类型,不能强转为Integer[]。应使用toArray(T[] a)方法。
public class ArrayListStudy4 {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
Integer[] objs = (Integer[])list.toArray();
//使用toArray(T[] a)方法
//Integer[] integers1 = list.toArray(new Integer[list.size()]);
}
}
报错:
Exception in thread "main" java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:13)
图5 list.toArray()后类型强转报错
Array.asList(T... a)返回的类型时Arrays的内部类ArrayList,没有实现add/remove/clear等方法;如果使用返回的对象调用add等方法,则会报错,如图6所示。
public class ArrayListStudy4 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(new Integer[]{1,2,3});
list.add(4);
}
}
报错:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:10)
图6 Array.asList(T... a)返回的值使用add方法报错
Array.asList(T... a)的源码如下图7所示。
public class Arrays {
@SafeVarargs
@SuppressWarnings("varargs")
public static <T> List<T> asList(T... a) {
return new ArrayList<>(a);
}
/**
* @serial include
*/
private static class ArrayList<E> extends AbstractList<E>
implements RandomAccess, java.io.Serializable
{
//没有实现add/remove/clear等方法
}
}
图7 Array.asList(T... a)的源码
5)泛型通配符<? entends T>来接受返回的数据,此写法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作为接口调用赋值时易出错。如图8所示,<? entends T>写法的泛型集合使用add方法报错,<? super T>写法的泛型使用get方法出错。
public class ArrayListStudy4 {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(new Integer[]{1,2,3});
list.add(4);
ArrayList<? extends Student> students = new ArrayList<Student>();
//运行报错
students.add(null);
ArrayList<? super Student> students2 = new ArrayList<Student>();
students2.add(new Student());
//运行报错
Object object = students2.get(0);
}
}
报错:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.AbstractList.add(AbstractList.java:148)
at java.util.AbstractList.add(AbstractList.java:108)
at com.study.collection.ArrayListStudy4.main(ArrayListStudy4.java:12)
图8 <? entends T>写法的泛型集合使用add方法报错,<? super T>写法使用get方法报错
6)在JDK7版本及以上,Comparator实现类要满足如下三个条件,不然Arrays.sort,Collections.sort会报IllegalArgumentException异常。三个条件如下:
a)x,y的比较结果和y,x的比较结果相反;
b) x>y,y>z,则x>z;
c)x=y,则x,z比较结果和y,z比较结果相同;
7)集合泛型定义时,在JDK7以上,使用diamond语法或全省略。
说明:菱形泛型,即diamond,直接使用<>来指代前面已经指定的类型。
//<>diamond方式
HashMap<String,String> userCache = new HashMap<>(16);
//全省略方式
ArrayList<User> users = new ArrayList(10);
3.ArrayList的线程安全
ArrayList的线程安全是指在并发操作ArrayList时,操作的结果是否会达到人们期望的效果。通常线程安全的方法是通过synchronized来保证原子性,可见性和顺序性,也就是要as-if-serial语义。如果没有考虑到代码中执行的原子性,这就可能会出现线程不安全的问题。
比如ArrayList就是线程不安全的。线程不安全的场景主要包括以下几种。
3.1 add方法
如图9多线程并发调用add()方法时,可能会出现图10和11的两种异常情况,一种是抛出ArrayIndexOutOfBoundsException的异常,另一种是ArrayList有些索引没有存入值(为null)。
public class ArrayListStudy {
public static ArrayList arrayList = new ArrayList();
//并发处理
public static void main(String[] args) {
ArrayList<Integer> str = new ArrayList<>();
Thread[] threadArr = new Thread[1000];
for(int i = 0;i<threadArr.length;i++){
//新建一个线程
threadArr[i] = new ArrayListThread();
//线程启动
threadArr[i].start();
}
for(int i=0;i<threadArr.length;i++){
try {
threadArr[i].join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int i=0;i<arrayList.size();i++){
System.out.println(arrayList.get(i));
}
}
}
class ArrayListThread extends Thread{
@Override
public void run(){
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
ArrayListStudy.arrayList.add(Thread.currentThread().getName());
}
}
图9 多线程并发调用add()方法
图10 并发调用add()方法时,出现数组越界异常(ArrayIndexOutOfBoundsException)的场景
图11 并发调用add()方法时,索引没有存入值的场景
3.2 remove方法
remove()可能出现集合某些索引的值没有移除的场景。
3.3 clone方法
如图12所示,clone()方法是对原list的浅拷贝,对list中的元素进行了浅拷贝,如果元素是对象,只拷贝元素的地址。拷贝的时候会调用Arrays.copyOf(elementData,size)方法,拷贝的时候有读取原数组元素的操作,是线程不安全的。
/**
* Returns a shallow copy of this <tt>ArrayList</tt> instance. (The
* elements themselves are not copied.)
*
* @return a clone of this <tt>ArrayList</tt> instance
*/
public Object clone() {
try {
ArrayList<?> v = (ArrayList<?>) super.clone();
v.elementData = Arrays.copyOf(elementData, size);
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
图12 ArrayList的clone()方法
3.4 iterator方法
iterator()获取的是ArrayList的内部类Itr。如图13所示,类Itr的next、previous、add、remove等方法是线程不安全的。且在使用iterator遍历时,如果使用了Iterator类之外的方法对list进行结构性修改(remove/add/clear等),则会抛出ConcurrentModificationException的异常;即fail-fast机制。
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
}
图13 ArrayList的内部类Itr
3.5 sublist方法
sublist()获取的是ArrayList内部类SubList的实例,如图14所示。它的set、get、add等方法是线程不安全的。
private class SubList extends AbstractList<E> implements RandomAccess {
public E set(int index, E e) {
rangeCheck(index);
checkForComodification();
E oldValue = ArrayList.this.elementData(offset + index);
ArrayList.this.elementData[offset + index] = e;
return oldValue;
}
public E get(int index) {
rangeCheck(index);
checkForComodification();
return ArrayList.this.elementData(offset + index);
}
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
}
图14 ArrayList内部类SubList
3.6 方法组合
方法组合是指对操作List的元素方法的组合。我以两种情况为例。一种情况(后文使用“组合一”引用)是ArrayList的get与迭代器中的remove方法的组合;另一种情况(后文使用“组合二”引用)是ArrayList的get与SubList的add方法的组合。由于ArrayList没有采取任何线程安全的策略,当然这两种情况都是线程不安全的。
3.7 线程安全的措施
ArrayList是线程不安全的,ArrayList的方法没有添加任何措施以保证线程安全。ArrayList只适用于单线程的场景。在多线程下,通常使用下述的3种方式来实现线程安全。
3.7.1 Vector
Vector(已废弃)的方法实现和ArrayList类似,只是是线程安全的[3]。如图15,Vector线程安全的策略是通过在方法上添加synchronized关键字来实现的。Vector的iterator方法获取内部类Itr,该类中的方法是线程安全的。Vector的subList方法获取SynchronizedList类,该类中的方法是线程安全的。但由于内部类Itr中remove方法使用的锁对象是Vector.this,与ArrayList.get方法使用的锁对象this不同,因此组合一是线程不安全的。如图16,subList方法的返回值SynchronizedList中的方法使用的锁对象是当前的Vector实例,与ArrayList.get方法的锁对象相同,因此组合二是线程安全的。
public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
//锁对象this
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
public synchronized Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
//锁对象Vector.this
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
}
public synchronized List<E> subList(int fromIndex, int toIndex) {
return Collections.synchronizedList(super.subList(fromIndex, toIndex),
this);
}
}
图15 Vector类中的get、iterator、subList方法
public class Collections {
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public void add(int index, E element) {
//锁对象为当前的Vector实例
synchronized (mutex) {list.add(index, element);}
}
}
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
}
}
图16 Collections.SynchronizedList中的方法
3.7.2 Collections.synchronizedList(ArrayList list)
可以通过Collections.synchronizedList(ArrayList list)方法实现线程安全[4]。如图17所示,该方法获取的SynchronizedList的线程安全是通过Synchronized(mutex)修饰代码块来实现的。SynchronizedList.iterator()返回值类型是ArrayList.Iterator(),它是线程不安全的。SynchronizedList.subList的返回值类型也是SynchonizedList,它是线程安全的,SynchronizedList.subList中的锁对象与原SynchronizedList相同,都是原SynchronizedList实例。显然,组合一是线程不安全的,组合二是线程安全的。
public class Collections {
public static <T> List<T> synchronizedList(List<T> list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public void add(int index, E element) {
//锁对象是当前Collections.SynchronizedList的实例
synchronized (mutex) {list.add(index, element);}
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
//锁对象为原SynchronizedList的实例,与add()方法相同
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
}
static class SynchronizedCollection<E> implements Collection<E>, Serializable {
final Collection<E> c; // Backing Collection
final Object mutex; // Object on which to synchronize
SynchronizedCollection(Collection<E> c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
SynchronizedCollection(Collection<E> c, Object mutex) {
this.c = Objects.requireNonNull(c);
this.mutex = Objects.requireNonNull(mutex);
}
public Iterator<E> iterator() {
//返回值类型ArrayList.iterator(),它是线程不安全的
return c.iterator(); // Must be manually synched by user!
}
}
}
图17 Collections中的内部类SynchronizedList和SynchronizedCollection
3.7.3 CopyOnWriteArrayList
也可以通过CopyOnWriteArrayList方法来实现线程安全[5]。如图18,在调用add方法时,将原数组copy到新数组,元素的新增在新数组中完成。新数组中新增元素后,在将当前的数组指向新数组。由于元素的新增时,setArray(newElements)的操作是原子性的,其它的修改数组的方法也是类似的(先操作copy的数组,然后通过调用setArray(new Elements)方法),所以get方法不需要再加锁。CopyOnWriteArrayList适用于多读的场景。CopyOnWriteArrayList的iterator获取的COWIterator类不支持remove、add等修改方法,因此COWIterator的其它的查询方法不需要加锁。所以组合一是线程安全的。CopyOnWriteArrayList的subList获取的COWSubList是线程安全的,锁对象是原CopyOnWriteArrayList实例的lock属性,与CopyOnWriteArrayList中方法的锁对象相同,因此组合二是线程安全的。
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
final transient ReentrantLock lock = new ReentrantLock();
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();
}
}
final void setArray(Object[] a) {
array = a;
}
//get方法不用加锁
public E get(int index) {
return get(getArray(), index);
}
public List<E> subList(int fromIndex, int toIndex) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (fromIndex < 0 || toIndex > len || fromIndex > toIndex)
throw new IndexOutOfBoundsException();
return new COWSubList<E>(this, fromIndex, toIndex);
} finally {
lock.unlock();
}
}
static final class COWIterator<E> implements ListIterator<E> {
//COWIterator.next()方法不用加锁
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
//COWIterator不支持remove()等修改方法
public void remove() {
throw new UnsupportedOperationException();
}
}
private static class COWSubList<E>
extends AbstractList<E>
implements RandomAccess
{
private final CopyOnWriteArrayList<E> l;
// only call this holding l's lock
COWSubList(CopyOnWriteArrayList<E> list,
int fromIndex, int toIndex) {
l = list;
expectedArray = l.getArray();
offset = fromIndex;
size = toIndex - fromIndex;
}
public void add(int index, E element) {
final ReentrantLock lock = l.lock;
lock.lock();
try {
checkForComodification();
if (index < 0 || index > size)
throw new IndexOutOfBoundsException();
l.add(index+offset, element);
expectedArray = l.getArray();
size++;
} finally {
lock.unlock();
}
}
}
}
图18 CopyOnWriteArrayList中的方法及内部类COWIterator和COWSubList
下表从常用方法get、add等,以及组合方法的维度对比了三种线程安全类。Vector和Collections.synchronizedList的实现大致相同,他们在多读场景下效率低,且组合一是线程不安全的。Vector和Collections.synchronizedList的不同之处包括iterator()方法获取的迭代器是否线程安全等。Vector中的迭代器实现了线程安全,但在方法组合时又线程不安全;且由于每个方法都使用了同步,效率低下,因此Vector已被弃用。相比而言,CopyOnWriteArrayList的读方法不用加锁,在多读场景下效率高,且组合一和组合二都是安全的。如果是多写场景,Collections.synchronizedList的效率更高,但要注意iterator()获取的迭代器中方法的线程安全,以及方法组合等场景下的线程安全。
表1 Vector、Collections.synchronizedList()、CopyOnWriteArrayList线程安全特性对比
Vector | Collections.synchronizedList() | CopyOnWriteArrayList | |
---|---|---|---|
get() | synchronized修饰(锁对象是this) | 锁对象this | 不用锁 |
add() | synchronized修饰 (锁对象是this) | 锁对象this | 使用l.lock加锁(l为复制数组) |
remove() | synchronized修饰(锁对象是this) | 锁对象this | 使用l.lock加锁(l为复制数组) |
clone() | synchronized修饰(锁对象是this) | 未实现 | 浅拷贝(只拷贝list,未拷贝list中的元素),不用锁 |
iterator()获取的迭代器类中的方法 | 方法内使用Vector.this加锁 | 未加锁,需要额外人工加锁 | 未加锁,仅支持previous、next等方法,不支持remove、add、set等方法 |
sublist() | this是锁对象 | 锁对象this | 使用l.lock加锁(l为复制数组) |
方法组合 | 组合一线程不安全,组合二线程安全 | 组合一线程不安全,组合二线程安全 | 组合一、组合二线程安全 |
参考资料:
[1] ArrayList源码中的类注释;
[2]《阿里巴巴Java开发手册 终极版 v1.3.0》;
[3] Vector源码;
[4]Collections.synchronizedList源码;
[5]CopyOnWriteArrayList源码;