List接口和实现类
list
接口继承自Collection接口,主要处理有序集合,所谓有序,就是存放在此数据结构的数据会排序。
arrayList
public class ArrayList extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
arraylist 继承自AbstractList
,底层数组实现。
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
空构造器,默认初始大小为10
/**
* Shared empty array instance used for default sized empty instances. We
* distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
* first element is added.
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //这里虽然复赋值为null数组,在第一次往里面添加元素时会扩大至容量10
}
有参构造
// initialCapacity表示list的初始容量
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
添加和移除操作:add() ,remove()
public void add(int index, E e) {
rangeCheckForAdd(index);
checkForComodification();
parent.add(parentOffset + index, e);
this.modCount = parent.modCount;
this.size++;
}
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
可以看出arraylist是非线程安全的,多线程操作需要程序员自己处理并发的问题。
linkedList
public class LinkedList
extends AbstractSequentialList
implements List, Deque, Cloneable, java.io.Serializable
可以看出linkedlist未继承AbstractList,而是自己实现list。继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。实现 List 接口,能对它进行队列操作。实现 Deque 接口,即能将LinkedList当作双端队列使用。实现了Cloneable接口,即覆盖了函数clone(),能克隆。实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
/**
*空构造器
*/
public LinkedList() {
}
添加元素,删除元素
public boolean add(E e) {
linkLast(e);
return true;
}
public boolean remove(Object o) {
if (o == null) {
for (Node x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
vector
public class Vector
extends AbstractList
implements List, RandomAccess, Cloneable, java.io.Serializable
可以看出,vector和arraylist有点类似,和arraylist最大的区别就是vector是线程安全的。
构造函数
/**
* initialCapacity是初始容量,capacityIncrement表示每次扩容时需 要扩大的量,当此参数小于等于0时,2倍扩容
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
/**
*
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
/**
*
*/
public Vector() {
this(10);
}
voctor的添加元素和删除元素方法
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
从这个方法可以看出,voctor实现同步的办法是加了synchronized。
一般情况下,arraylist遍历元素时使用foreach,因为arraylist底层是数组实现,查询比较快。而linkedlist底层是双向链表,对插入和删除性能比较好,在遍历时用foreach效率就会很低(此处更正,不只foreach效率低,并且使用foreach遍历list无论是否多线程操作都会发生fail-fast错误。),一般就会使用到Iterator(称为迭代器) 来提高效率,因为创建它的代价很小。但在多线程时对它使用不当会发生一个叫fail-fast(称为快速失败)的问题,也就是会报java.util.ConcurrentModificationException,原因在于iterator是对linkedlist中对象的引用的一个拷贝,比如:
Object o = new Object();
//o就是对new Object()对象的引用,相当于一个指针指向在堆中的真实对象,而对对象引用的拷贝是指用一个新的引用指向o这个引用。
Object ob = o;
在iterator容器中全是对真实对象引用的拷贝。层层寻找,我们找到了这个方法。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
就是这个方法抛出了这个异常,modCount这个参数我们发现在abstractList中
//modCount是抽象类AbstractList中的变量,默认为0,用于记录集合操作过程中作的修改次数
protected transient int modCount = 0;
expectedModCount 存在abstractList中,初始等于modCount;
//expectedModCount表示迭代器对集合进行修改的次数。
int expectedModCount = modCount;
这里不难分析出,一个是表示线程对集合修改次数,一个地iterator对集合修改次数。当两个值不相等,抛出异常。假设场景:当一个线程对iterator遍历时(假设它正在遍历,不存在添加修改),expectedModCount就不会改变。而另外一个线程同时在删除或添加元素,modCount就是改变。这就导致这两个值不一致,报错。
public E next() {
checkForComodification(); //遍历前检查
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException();
}
}
和我们猜测的一样每次next(),都会检查的,解决办法就是保证容器的操作原子性,加锁,同步,当然,也可以使用java.util.concurrent包下的很多实现了线程安全的容器。
~~~~划水~
通过查api文档知道,arraylist,linkedlist是在java.util包下线程不安全的集合类,vorctor虽然是线程安全的,但它使用的是synchronized来实现线程安全,它的效率太低了。今天我们来说java.util.concurrent包下的线程安全的集合类。
CopyOnWriteArrayList,通过名字就知道,它的策略是在写的时候复制list,简单的讲,一个线程在对list写的时候,不是直接对list写,而是对list进行复制,对复制出来的list写,写完后在把引用指向这个复制的list。在写的期间不影响其他线程对list的访问。
看看它的读写方法
//可以看到读方法是没有加锁的
@SuppressWarnings("unchecked")
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
//写方法手动加锁
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
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();
}
}
public void add(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (index > len || index < 0)
throw new IndexOutOfBoundsException("Index: "+index+
", Size: "+len);
Object[] newElements;
int numMoved = len - index;
if (numMoved == 0)
newElements = Arrays.copyOf(elements, len + 1);
else {
newElements = new Object[len + 1];
System.arraycopy(elements, 0, newElements, 0, index);
System.arraycopy(elements, index, newElements, index + 1,
numMoved);
}
newElements[index] = element;
setArray(newElements);
} finally {
lock.unlock();
}
}
写方法为什么加锁呢,是为了在一个线程对list复制时其他线程不能复制,写完后把引用指向新的list时其他线程才能对新的list进行拷贝进行写操作。这样保证了线程安全,注意这样的list,**线程不能及时获取到准确的数据。应用于读多写少的场景。比如黑名单。
那么问题来了,CopyOnWriteArrayList不能满足我们想的需求啊,我们希望及时获取到被修改的数据,但又不想用vector(因为它效率低,读方法和写方法都是synchronized)。那怎么办呢,那么接下来我们就该聊聊集合工具类Collections提供的synchronizedList方法了。它是个静态方法,不需要new。我们看看它的源码吧!
public static List synchronizedList(List list) {
return (list instanceof RandomAccess ?
new SynchronizedRandomAccessList<>(list) :
new SynchronizedList<>(list));
}
可以看出,它需要传入一个list对象,然后看它是否属于RandomAccess的子类(instanceof判断一个对象是否属于某个类),我们研究看看RandomAccess这个类是个啥。
public interface RandomAccess {
}
源代码就这么点,它就是接口,而且没有定义任何方法。那就百度查查是干啥的。
RandomAccess 就是一个标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的List中性能更加高效(在Collections二分查找时)。原来是提高效率的。
假如我们需要同步arraylist,可以查到,arraylist是实现了这个接口的。说明arraylist instanceof RandomAccess 返回为true,程序便会new SynchronizedRandomAccessList<>(list)。现在该研究研究SynchronizedRandomAccessList是个啥了。
SynchronizedRandomAccessList(List list) {
super(list);
}
继续往里面走
SynchronizedList(List list) {
super(list);
this.list = list;
}
this.list是啥
static class SynchronizedList extends SynchronizedCollection
implements List {
private static final long serialVersionUID = -7754090372962971524L;
final List list;
... //后面代码略
原来是Collections类自己实现的的一个静态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);}
}
又是synchronized,可这个mutex是啥玩意,对他加锁就可以实现解决多线程问题?
static class SynchronizedCollection implements Collection, Serializable {
private static final long serialVersionUID = 3053995032091335093L;
final Collection c; // Backing Collection
final Object mutex; // Object on which to synchronize
...//其他代码略
这有是啥呢?先看看它指向哪个对象?
SynchronizedCollection(Collection c) {
this.c = Objects.requireNonNull(c);
mutex = this;
}
默认指向自己呐!大家可注意到SynchronizedList
是继承了SynchronizedCollection 的,这两个类都是Collections类自己实现的list容器。原来搞了半天,是对自己的容器对象加锁(默认情况,也可以指定加锁对象!)这里我们应该差不多可以总结SynchronizedList和vector的区别了,SynchronizedList是对代码块实现同步。vector是对整个方法实现同步。哈哈,SynchronizedList效率确实会高那么点点的。还有一个很大不同是它俩扩容机制,vector扩容是2倍增长,arraylist每次扩容是前一次的50%.
还有一个不同是SynchronizedList这个静态方法也可以把linkedlist实现同步。哈哈,vector却不能啊! 具体哪个场景用哪个,就得自己斟酌了。
~~~~划水~