一、关于Vector类的注意事项

1、从 Java 2 平台 v1.2 开始,vector类改进为实现 List 接口,成为 Java Collections Framework 的成员;所以vector类有一些遗留的方法。

2、关于Vector的线程安全:Vector中的单个方法是线程安全的,因为方法加了synchronized修饰;但是对于复合操作(一段代码调用Vector类的多个方法),就不能保证线程安全了。后面有例子来具体说明这一点。

3、Oracle官方文档:

  If you need synchronization, a Vector will be slightly faster than an ArrayList synchronized with Collections.synchronizedList. But Vector has loads of legacy operations, so be careful to always manipulate the Vector with the List interface or else you won't be able to replace the implementation at a later time.     ----摘自 https://docs.oracle.com/javase/tutorial/collections/implementations/list.html

4、即使要线程安全,通常也不用Vector,因为:

  Vector synchronizes on each individual operation. That's almost never what you want to do. Generally you want to synchronize a whole sequence of operations. you can decorate a collection using the calls such as Collections.synchronizedList.  ---- 摘自 https://stackoverflow.com/questions/1386275/why-is-java-vector-and-stack-class-considered-obsolete-or-deprecated

5、由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的(fail-fast:如果在迭代器创建后的任意时间从结构上修改了Vector(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。Vector 的 elements 方法返回的 Enumeration 不是 快速失败的。

  注意,迭代器的快速失败行为不能得到保证,一般来说,存在不同步的并发修改时,不可能作出任何坚决的保证。快速失败迭代器尽最大努力抛出 ConcurrentModificationException。因此,编写依赖于此异常的程序的方式是错误的,正确做法是:迭代器的快速失败行为应该仅用于检测 bug。

二、Vector类真的线程安全吗?

  通常我们说Vector类时线程安全的类,是指Vector中的单个方法是线程安全的,因为方法加了synchronized修饰;但是对于复合操作(一段代码调用Vector类的多个方法),就不能保证线程安全了。下面通过一个案例来具体说明:

代码:

public class VectorTest {
public static void main(String[] args) throws Exception {
Vector<Integer> vector = new Vector<>();
// 添加数据
for (int i = 0; i < 10; i++) {
vector.add(i);
}
printVector(vector); // 开启两个新线程,这两个线程共享对象vector
new Thread(new MyRunnable(vector)).start();
new Thread(new MyRunnable(vector)).start();
} private static void printVector(Vector<?> v) {
System.out.println("vector.size():" + v.size());
int size = v.size();
for (int i = 0; i < size; i++) {
System.out.println("index=" + i + ":" + v.get(i));
}
System.out.println("=== 分割线 ===");
}
} public class MyRunnable implements Runnable {
private Vector<?> vector; public MyRunnable(Vector<?> vector) {
this.vector = vector;
} @Override
public void run() {
System.out.println("开启线程:" + Thread.currentThread().getName() + "...");
int size = vector.size(); // size=10
System.out.println("删除index=" + (size - 1) + "的元素");
vector.remove(size - 1); // 删除最后一个元素(索引为9) try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.print("查看index=" + (size - 2) + "的元素的值:");
System.out.println(vector.get(size - 2)); // 查看索引为8的元素
}
}

控制台打印结果:

vector.size():10
index=0:0
index=1:1
index=2:2
index=3:3
index=4:4
index=5:5
index=6:6
index=7:7
index=8:8
index=9:9
=== 分割线 ===
开启线程:Thread-0...
删除index=9的元素
开启线程:Thread-1...
删除index=8的元素
查看index=7的元素的值:7
查看index=8的元素的值:Exception in thread "Thread-0" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 8
at java.util.Vector.get(Vector.java:748)
at com.oy.demo1.MyRunnable.run(MyRunnable.java:27)
at java.lang.Thread.run(Thread.java:745)

分析:

  1)在主线程中创建了两个新线程,默认命名为Thread-0和Thread-1;

  2)首先开启了线程Thread-0,然后删除index=9的元素,然后执行Thread.sleep(100),线程Thread-0进入休眠,此时线程Thread-0并不持有锁(即对象vector),因为在执行完remove()方法后就将锁释放了;

  3)线程Thread-1获取到CPU执行权,此时线程Thread-0并不持有锁,所以线程Thread-1调用remove()方法删除index=8的元素,然后释放锁;

  4)线程Thread-0再次获取到CPU执行权,需要调用get()方法查看index=8的元素的值时,报ArrayIndexOutOfBoundsException异常,因为index=8的元素已经被线程Thread-1删除了。

  5)其实程序是否有线程安全问题很容易判断。只需判断:

    (1)是否多线程环境;

    (2)使用有共享变量;

    (3)是否有多条语句操作共享变量;

  显然程序是多线程环境,共享变量vector,MyRunnable类的run()方法中也有多条语句操作共享变量,所以出现线程安全问题是无法避免的。那么如何解决呢?很简单,将run()方法中多条语句操作共享变量的代码套在synchronized代码块中即可。

synchronized (vector) {
System.out.println("开启线程:" + Thread.currentThread().getName() + "...");
int size = vector.size(); // size=10
System.out.println("删除index=" + (size - 1) + "的元素");
vector.remove(size - 1); // 删除最后一个元素(索引为9) try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} System.out.print("查看index=" + (size - 2) + "的元素的值:");
System.out.println(vector.get(size - 2)); // 查看索引为8的元素
}

三、迭代器的快速失败(fail-fast)机制

  1、前面提到:由 Vector 的 iterator 和 listIterator 方法所返回的迭代器是快速失败的(fail-fast:如果在迭代器创建后的任意时间从结构上修改了Vector(通过迭代器自身的 remove 或 add 方法之外的任何其他方式),则迭代器将抛出 ConcurrentModificationException。因此,面对并发的修改,迭代器很快就完全失败,而不是冒着在将来不确定的时间任意发生不确定行为的风险。

  2、一个案例:测试迭代器的fail-fast机制

代码:

public class VectorTest {
public static void main(String[] args) throws Exception {
Vector<Integer> vector = new Vector<>();
// 添加数据
for (int i = 0; i < 10; i++) {
vector.add(i);
}
printVector(vector); // 开启一个新线程,该线程与main线程共享变量vector
// 该线程的功能:循环删除vector内存储的所有元素
new Thread(new MyRunnable(vector), "Thread-new").start(); // printVector(vector); // ===> ①
printVector2(vector); // ===> ②
} private static void printVector(Vector<?> v) {
System.out.println("线程" + Thread.currentThread().getName() + "查看vector.size():" + v.size());
int size = v.size();
for (int i = 0; i < size; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "查看index=" + i + "的元素的值: " + v.get(i));
}
System.out.println("=== 分割线 ===");
} private static void printVector2(Vector<Integer> v) {
System.out.println("线程" + Thread.currentThread().getName() + "查看vector.size():" + v.size());
for (Integer i : v) { // foreach循环底层是通过迭代器实现的
System.out.println("线程" + Thread.currentThread().getName() + "查看index=" + i + "的元素的值: " + i);
}
System.out.println("=== 分割线 ===");
}
}
public class MyRunnable implements Runnable {
private Vector<?> vector;
public MyRunnable(Vector<?> vector) {
this.vector = vector;
} @Override
public void run() { // 刪除vector内存储的所有元素
synchronized (vector) {
int size = vector.size();
for (int i = 0; i < size; i++) {
System.out.println("线程" + Thread.currentThread().getName() + ":删除第" + (i + 1) + "个元素");
vector.remove(0);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}

控制台结果:

  1)注释掉代码中的语句②(标红),放开语句①,得到的结果是:

线程main查看vector.size():10
线程main查看index=0的元素的值: 0
线程main查看index=1的元素的值: 1
线程main查看index=2的元素的值: 2
线程main查看index=3的元素的值: 3
线程main查看index=4的元素的值: 4
线程main查看index=5的元素的值: 5
线程main查看index=6的元素的值: 6
线程main查看index=7的元素的值: 7
线程main查看index=8的元素的值: 8
线程main查看index=9的元素的值: 9
=== 分割线 ===
线程main查看vector.size():10
线程main查看index=0的元素的值: 0
线程main查看index=1的元素的值: 1
线程main查看index=2的元素的值: 2
线程main查看index=3的元素的值: 3
线程main查看index=4的元素的值: 4
线程main查看index=5的元素的值: 5
线程main查看index=6的元素的值: 6
线程Thread-new:删除第1个元素 // 此时线程Thread-new抢到了CPU的执行权,开始删除vector的元素。
线程Thread-new:删除第2个元素
线程Thread-new:删除第3个元素
线程Thread-new:删除第4个元素
线程Thread-new:删除第5个元素
线程Thread-new:删除第6个元素
线程Thread-new:删除第7个元素
线程Thread-new:删除第8个元素
线程Thread-new:删除第9个元素
线程Thread-new:删除第10个元素
                   // 线程main抢到了CPU的执行权,本要查看index=7的元素的值,但是此时线程
                   // Thread-new已经将vector的所有元素删除了,所以报错。
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 7
at java.util.Vector.get(Vector.java:748)
at com.oy.VectorTest.printVector(VectorTest.java:27)
at com.oy.VectorTest.main(VectorTest.java:19)

  解决方法:将printVector()方法里面的代码用synchronized包起来:

private static void printVector(Vector<?> v) {
synchronized (v) {
System.out.println("线程" + Thread.currentThread().getName() + "查看vector.size():" + v.size());
int size = v.size();
for (int i = 0; i < size; i++) {
System.out.println("线程" + Thread.currentThread().getName() + "查看index=" + i + "的元素的值: " + v.get(i));
}
System.out.println("=== 分割线 ===");
}
}

  2)注释掉代码中的语句①(标红),放开语句②,得到的结果是:

线程main查看vector.size():10
线程main查看index=0的元素的值: 0
线程main查看index=1的元素的值: 1
线程main查看index=2的元素的值: 2
线程main查看index=3的元素的值: 3
线程main查看index=4的元素的值: 4
线程main查看index=5的元素的值: 5
线程main查看index=6的元素的值: 6
线程main查看index=7的元素的值: 7
线程main查看index=8的元素的值: 8
线程main查看index=9的元素的值: 9
=== 分割线 ===
线程main查看vector.size():10 // 线程main抢到了CPU的执行权,本要通过迭代器查看vector的元素,但是同时
                  // 有其他线程正在删除vector的元素,所以报ConcurrentModificationException异常。
线程Thread-new:删除第1个元素
线程Thread-new:删除第2个元素
线程Thread-new:删除第3个元素
线程Thread-new:删除第4个元素
线程Thread-new:删除第5个元素
线程Thread-new:删除第6个元素
线程Thread-new:删除第7个元素
线程Thread-new:删除第8个元素
线程Thread-new:删除第9个元素
线程Thread-new:删除第10个元素
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Vector$Itr.checkForComodification(Vector.java:1184)
at java.util.Vector$Itr.next(Vector.java:1137)
at com.oy.VectorTest.printVector2(VectorTest.java:36)
at com.oy.VectorTest.main(VectorTest.java:20)

  解决方法:将printVector2()方法里面的代码用synchronized包起来:

private static void printVector2(Vector<Integer> v) {
synchronized (v) {
System.out.println("线程" + Thread.currentThread().getName() + "查看vector.size():" + v.size());
for (Integer i : v) {
System.out.println("线程" + Thread.currentThread().getName() + "查看index=" + i + "的元素的值: " + i);
}
System.out.println("=== 分割线 ===");
}
}

四、Vector类的扩容机制

  1、通过构造方法 public Vector(int initialCapacity, int capacityIncrement) 指定初始容量和容量增量;

  2、主要的扩容方法:grow(int minCapacity)

    1)如果容量增量>0,新容量=旧容量+增量; 如果容量增量<=0,新容量=旧容量*2;

    2)然后新容量与minCapacity比较,较大者作为容量的最终值;

    3)minCapacity通过 ensureCapacity(int minCapacity) 方法指定;

  3、小案例

Vector<Integer> v = new Vector<>(11);
for (int i = 0; i < 10; i++) {
v.add(i);
}
System.out.println("v.capacity(): " + v.capacity()); // v.ensureCapacity(21); // minCapacity=21
// 容量增量为0,所以新容量=旧容量*2=11*2=22
// 新容量与minCapacity比较,较大者作为容量的最终值
System.out.println("v.capacity(): " + v.capacity()); // 22 //v.ensureCapacity(23); // minCapacity=23
//容量增量为0,所以新容量=旧容量*2=11*2=22
//新容量与minCapacity比较,较大者作为容量的最终值
//System.out.println("v.capacity(): " + v.capacity()); // 23 //v.add(10);
//System.out.println("v.capacity(): " + v.capacity()); // 11
//v.add(11); // 此时集合已满,再添加一个元素,集合需要扩容
//System.out.println("v.capacity(): " + v.capacity()); //

  4、Vector类的部分源码(jdk1.8.0_111)

public class Vector extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable { // 底层是数组Object[]实现的
protected Object[] elementData; // Vector集合的元素个数
protected int elementCount; /**
* 容量增量
* The amount by which the capacity of the vector is automatically
* incremented when its size becomes greater than its capacity. If
* the capacity increment is less than or equal to zero, the capacity
* of the vector is doubled each time it needs to grow.
*/
protected int capacityIncrement; /**
* 构造方法:指定初始容量和容量增量
* @param initialCapacity 初始容量
* @param capacityIncrement 容量增量
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
// 创建一个长度为initialCapacity(初始容量)的数组
this.elementData = new Object[initialCapacity];
// 初始化成员变量capacityIncrement(容量增量)
this.capacityIncrement = capacityIncrement;
} /**
* 构造方法 :指定初始容量
* @param initialCapacity 初始容量
*/
public Vector(int initialCapacity) {
this(initialCapacity, 0);
} /**
* 无参构造,初始容量为10
*/
public Vector() {
this(10);
} /**
* 构造方法:传入一个Collection集合。
* @param c
*/
public Vector(Collection<? extends E> c) {
elementData = c.toArray(); // 将集合转成Object数组
elementCount = elementData.length; // Vector集合的元素个数
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
} // 获取Vector集合的容量
public synchronized int capacity() {
return elementData.length; // 即底层Object数组的长度
} // 获取Vector集合实际的元素个数
public synchronized int size() {
return elementCount;
} /**
* @param minCapacity the desired minimum capacity
*/
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
} private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
} // 主要的扩容方法
private void grow(int minCapacity) {
int oldCapacity = elementData.length; // Vector集合当前的容量
// 如果容量增量>0,容量=容量+增量;
// 如果容量增量<=0,容量=容量*2;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity); // 然后与minCapacity比较
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
}

参考资料:

  1)https://docs.oracle.com/javase/tutorial/collections/implementations/list.html

  2)https://www.jianshu.com/p/a20052ac48f1

  3)https://stackoverflow.com/questions/1386275/why-is-java-vector-and-stack-class-considered-obsolete-or-deprecated

  4)https://blog.csdn.net/shecanwin/article/details/70846690

04-19 21:41