Vector集合是 jdk1.0 版本就提供的集合类,它的所有操作集合的方法,都进行了加锁(就是对方法使用synchronized修饰)。这样就可以防止多线程同时修改集合。
因为是方法级synchronized锁,那么Vector集合的效率就不高。而且使用了Vector集合并不一定在多线程情况下就没有问题。
一. Vector不一定就是线程安全
很多人觉得不可思议,我们已经使用方法级synchronized锁,那么同一时刻只能由一个线程来操作集合啊,怎么还会有线程安全问题。
使用synchronized锁,我们可以保证对集合操作的原子性,这个是毋庸置疑的。但是我们对集合遍历却是两个操作。想一想我们怎么使用迭代器Iterator进行集合遍历的,我们是先调用hasNext方法,再调用next方法获取值。我们即使给两个方法都加了锁,保证操作原子性。但是它们合起来却不是原子性的。
那么就有可能出现下面情况,我们使用hasNext方法返回true,说明集合中还有值,正准备调用next方法时,另一个线程突然将这个值移除了(因为hasNext方法和next方法合起来没有加锁),这个就是问题产生原因。
因为hasNext方法和next方法是由用户自己调用的,我们没办法要求用户进行加锁,而这种情况可能会发生,怎么办呢?所以就用ConcurrentModificationException这个异常来代表这个情况。所以现在可以明白AbstractList中成员属性modCount的重要作用了吧。
注Vector提供的枚举器Enumeration却没有这个功能,它不能判断多线程环境下进行遍历时,是否有其他线程修改这个集合。只有当发生最严重的情况,即集合中只剩下最后一个元素,却被删除了,这时调用nextElement方法,抛出NoSuchElementException异常。
具体实例代码在文中末尾,所以Vector这个集合尽量少用,因为它既不能保证多线程一定安全,而且效率还低。
二. 成员属性
public class Vector<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable{}
可以看出Vector和ArrayList所继承和实现的接口一模一样。
// 用elementData数组来存储集合中的元素
protected Object[] elementData;
// 集合中元素数量
protected int elementCount;
// 每次扩容时,多增加的大小。如果是0的话,那么每次扩容的时候,都是原数组长度的一倍
protected int capacityIncrement;
对比ArrayList,Vector的成员属性更加简洁一点。
- elementData Object数组用来存储集合中元素。
- elementCount表示集合中元素的数量。
- capacityIncrement 表示数组每次扩容固定增加的数量。
- 还有一个重要属性就是继承自AbstractList的modCount属性。
三. 构造函数
3.1 默认构造函数
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
Vector构造函数就比ArrayList简单明了多了,空参构造函数和一个参数构造函数都是通过默认值,来调用两个参数构造函数。主要是用来创建对应长度的数组。
注意这里没有进行数组的延迟创建,所以没创建一个Vector集合,都会创建一个数组。
3.2 通过Collection实例构建集合
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
// c.toArray()方法返回的可能并不是Object[]数组
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
通过toArray方法,将集合c转成数组,然后赋值给elementData变量。还要注意一点就是,c.toArray()方法返回的可能并不是Object[]数组,所以这里做了判断。
四. 重要方法
比较重要的方法,第一个就是数组扩容方法
4.1 数组扩容方法
// 这个方法是给集合外部调用的,确保数组长度不能小于集合元素数量
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
这个方法给集合外部调用的,来确保数组长度不能小于集合元素数量。因为是外部调用,所以使用synchronized进行加锁。
// Vector集合内部调用,新添数据的时候会调用。 确保数组长度不能小于集合元素数量
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
集合内部调用,新添数据的时候会调用。这里没有使用synchronized加锁,是因为调用的地方,都已经加过锁了。
private void grow(int minCapacity) {
// 获取老数组长度
int oldCapacity = elementData.length;
// 如果capacityIncrement大于0,那么就增加这个固定长度。
// 否则就是将老数组长度扩大一倍
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
// 如果进行扩容之后,还是小于minCapacity值,那么就直接用minCapacity值当成数组长度
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
// 处理newCapacity值大于MAX_ARRAY_SIZE情况,集合容量也是有上限的。
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// 使用Arrays.copyOf方法,将老数组的元素拷贝到newCapacity长度的新数组中,并返回这个新数组。
elementData = Arrays.copyOf(elementData, newCapacity);
}
进行数组扩容,如果capacityIncrement大于0,那么就在原长度基础上加上这个固定数值capacityIncrement,否则就将原长度扩大一倍。如果还是小于minCapacity,那么就直接使用minCapacity作为新数组长度,然后再处理超出最大数组长度问题。最后使用Arrays.copyOf方法,将老数组元素拷贝到新数组中。
4.2 添加元素
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
在集合末尾添加元素,先确保数组容量,然后再在集合末尾添加元素,最后将集合数量size加一。
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized void insertElementAt(E obj, int index) {
modCount++;
if (index > elementCount) {
throw new ArrayIndexOutOfBoundsException(index
+ " > " + elementCount);
}
ensureCapacityHelper(elementCount + 1);
System.arraycopy(elementData, index, elementData, index + 1, elementCount - index);
elementData[index] = obj;
elementCount++;
}
add(int index, E element)方法是调用insertElementAt方法,而且在add方法中没有加锁,因为在add方法中没有操作集合。
在数组index下标位置插入一个元素,先确保数组长度,再调用System.arraycopy方法,将index位置之后的元素右移一位。然后在index位置存入新添加数据,最后将集合数量elementCount自增。
public synchronized boolean addAll(Collection<? extends E> c) {
modCount++;
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
System.arraycopy(a, 0, elementData, elementCount, numNew);
elementCount += numNew;
return numNew != 0;
}
在集合末尾添加一个集合c中全部元素,将集合c转化成一个数组a,确保本集合数组长度够用,通过System.arraycopy方法将数组a中所有元素全部拷贝到本集合末尾,最后将集合数量增加。
public synchronized boolean addAll(int index, Collection<? extends E> c) {
modCount++;
if (index < 0 || index > elementCount)
throw new ArrayIndexOutOfBoundsException(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityHelper(elementCount + numNew);
int numMoved = elementCount - index;
if (numMoved > 0)
System.arraycopy(elementData, index, elementData, index + numNew,
numMoved);
System.arraycopy(a, 0, elementData, index, numNew);
elementCount += numNew;
return numNew != 0;
}
与addAll(Collection<? extends E> c)相比,它多了将index位置之后的元素全部右移新添加集合数量numNew的长度。正好可以空出index 到index + numNew位置来存放添加的集合。
4.3 删除元素
public boolean remove(Object o) {
return removeElement(o);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
public synchronized void removeElementAt(int index) {
modCount++;
if (index >= elementCount) {
throw new ArrayIndexOutOfBoundsException(index + " >= " +
elementCount);
}
else if (index < 0) {
throw new ArrayIndexOutOfBoundsException(index);
}
int j = elementCount - index - 1;
if (j > 0) {
System.arraycopy(elementData, index + 1, elementData, index, j);
}
elementCount--;
elementData[elementCount] = null; /* to let gc do its work */
}
删除某个元素最终都会调用删除某个位置index的removeElementAt方法。而删除数组某个位置元素,其实只要将index之后位置元素向前移动一位就可以了,注意一点 要将数组中无效位置的数据置位null(而这里的无效位置就是elementCount)。
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
与removeElementAt方法流程一样,只不过这个方法返回了被删除元素。
public synchronized boolean removeAll(Collection<?> c) {
return super.removeAll(c);
}
public synchronized boolean retainAll(Collection<?> c) {
return super.retainAll(c);
}
这里复写方法,就是为了给该方法加上synchronized锁,这个不改变实现逻辑,进行同步的最简单方式,你会发现Collections工具类提供的synchronizedXXX方法都是这个做的。
和ArrayList相比,Vector没有做什么优化,而是调用AbstractCollection对应方法,是通过迭代器的remove方法进行删除的。
public void clear() {
removeAllElements();
}
public synchronized void removeAllElements() {
modCount++;
// Let gc do its work
for (int i = 0; i < elementCount; i++)
elementData[i] = null;
elementCount = 0;
}
加了synchronized锁,将数组中所有元素置位null,方便垃圾回收器回收。
public synchronized void setSize(int newSize) {
modCount++;
if (newSize > elementCount) {
ensureCapacityHelper(newSize);
} else {
for (int i = newSize ; i < elementCount ; i++) {
elementData[i] = null;
}
}
elementCount = newSize;
}
这个是Vector独有的比较有意思的方法,它也可以删除集合中的元素。如果新设的数量newSize比原数量elementCount小,那么数组newSize之后的元素全部置位null。
4.4 替换元素
public synchronized E set(int index, E element) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
就是将数组对应位置替换成新元素element
4.5 查询方法
E elementData(int index) {
return (E) elementData[index];
}
返回对应索引位置元素,并强转成E类型。注意这个方法也没有使用synchronized。所以它肯定在别的synchronized方法中调用的,第二这个方法的修饰符是default,即外部是没有办法调用它的。
public synchronized E get(int index) {
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
return elementData(index);
}
通过elementData方法获取元素。
public int indexOf(Object o) {
return indexOf(o, 0);
}
public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
正向遍历数组,查找与o相同元素,如果查找到就返回对应下标位置,如果没有查到就返回-1。
public synchronized int lastIndexOf(Object o) {
return lastIndexOf(o, elementCount-1);
}
public synchronized int lastIndexOf(Object o, int index) {
if (index >= elementCount)
throw new IndexOutOfBoundsException(index + " >= "+ elementCount);
if (o == null) {
for (int i = index; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = index; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
反向遍历数组,查找与o相同元素,如果查找到就返回对应下标位置,如果没有查到就返回-1。
public boolean contains(Object o) {
return indexOf(o, 0) >= 0;
}
contains方法是通过indexOf方法实现的,它也没有使用synchronized,因为在indexOf方法中会使用同步。
public synchronized Iterator<E> iterator() {
return new Itr();
}
public synchronized ListIterator<E> listIterator() {
return new ListItr(0);
}
public synchronized ListIterator<E> listIterator(int index) {
if (index < 0 || index > elementCount)
throw new IndexOutOfBoundsException("Index: "+index);
return new ListItr(index);
}
与AbstractList一样,Vector提供两个迭代器,都是Vector内部类。
五. Vector的内部类Itr
Vector的内部类Itr 与ArrayList的内部类Itr的实现几乎一样。只不过是加了synchronized锁。
5.1 成员属性
// 光标索引位置,0表示在集合开始位置(因为这是一个list集合,所以可以用索引表示开始位置)
int cursor;
// 表示读取到的当前元素位置
int lastRet = -1;
// 用来判断原集合是否被修改,抛出ConcurrentModificationException异常。
int expectedModCount = modCount;
5.2 遍历集合
public boolean hasNext() {
return cursor != elementCount;
}
注意一下,这个方法居然没有使用synchronized。
其实从第一节分析可知,即使这里使用了synchronized,也没有办法保证多线程安全。因为hasNext方法和next方法不能保证一起的原子性。
而checkForComodification方法,来判断迭代器遍历的时候,是否有其他线程修改原集合,如果有就抛出ConcurrentModificationException异常。所以说它并不是一个解决方案,它只是明确告诉你这个操作有多线程并发异常。
如果你想保证集合在多线程下也是安全的,最简单地方式是使用并发容器CopyOnWriteArrayList,但是它没办法保证元素的实时性。这个在java并发框架里会说。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
public E next() {
synchronized (Vector.this) {
checkForComodification();
int i = cursor;
if (i >= elementCount)
throw new NoSuchElementException();
cursor = i + 1;
return elementData(lastRet = i);
}
}
我们知道在Vector类中非静态synchronized方法,使用的锁就是这个Vector类的实例this,也就是这里的Vector.this。
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
主要是对移除操作加synchronized同步代码块。
另一个内部类ListItr 和这个也一样,主要功能就是对操作集合的方法加了synchronized同步代码块。
总结
Vector是jdk1.0 提供的集合类,通过synchronized同步锁,保证了对集合单个操作的原子性,但是经过分析,我们发现Vector集合并不能保证在多线程环境下是线程安全的。
主要是因为我们在遍历集合时,要进行两个操作,一个是判断集合中是否有值,一个是获取集合中元素。你只能保证这两个操作各自的原子性,但是它们合在一起的原子性却没有办法保证。
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
public class FastFailTest1 {
// 使用ArrayList集合,有多线程安全问题
// private static List<Integer> list = new ArrayList<>();
// // 使用Vector集合,有多线程安全问题
private static List<Integer> list = new Vector<>();
//
// // 使用CopyOnWriteArrayList集合,没有多线程安全问题,但是它有添加元素没有办法立即读取到
// private static List<Integer> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) {
new Thread(new WriteRunnable(0),"Thread1").start();
new Thread(new WriteRunnable(10), "Thread2").start();
// new Thread(new WriteRunnable(30), "Thread3").start();
}
private static void readList() {
Iterator<Integer> iterator = list.iterator();
//
System.out.println("thread=="+Thread.currentThread().getName());
while (iterator.hasNext()) {
// 这里使用sleep方法,来模拟特殊情况,人为地使迭代器的两个方法产生时间间隔。
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(" "+iterator.next());
}
System.out.println();
}
static class WriteRunnable implements Runnable {
int start;
WriteRunnable (int start) {
this.start = start;
}
@Override
public void run() {
for (int i = start; i < start+5; i++) {
list.add(i);
readList();
}
}
}
}
网友评论