谈一谈Vector类
一、关于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
谈一谈Vector类的更多相关文章
- 浅谈C++ STL vector 容器
浅谈C++ STL vector 容器 本篇随笔简单介绍一下\(C++STL\)中\(vector\)容器的使用方法和常见的使用技巧.\(vector\)容器是\(C++STL\)的一种比较基本的容器 ...
- 谈一谈Java8的函数式编程(二) --Java8中的流
流与集合 众所周知,日常开发与操作中涉及到集合的操作相当频繁,而java中对于集合的操作又是相当麻烦.这里你可能就有疑问了,我感觉平常开发的时候操作集合时不麻烦呀?那下面我们从一个例子说起. 计 ...
- 谈一谈泛型(Generic)
谈一谈泛型 首先,泛型是C#2出现的.这也是C#2一个重要的新特性.泛型的好处之一就是在编译时执行更多的检查. 泛型类型和类型参数 泛型的两种形式:泛型类型( 包括类.接口.委托和结构 没有泛型枚 ...
- 谈一谈深度学习之semantic Segmentation
上一次发博客已经是9月份的事了....这段时间公司的事实在是多,有写博客的时间都拿去看paper了..正好春节回来写点东西,也正好对这段时间做一个总结. 首先当然还是好好说点这段时间的主要工作:语义分 ...
- 谈一谈并查集QAQ(上)
最近几日理了理学过的很多oi知识...发现不知不觉就有很多的知识忘记了... 在聊聊并查集的时候顺便当作巩固吧.... 什么是并查集呢? ( Union Find Set ) 是一种用于处理分离集合的 ...
- 谈一谈C#的事件
谈一谈C#的事件 C#中事件基于委托,要理解事件要先理解委托,如果觉得自己关于委托不是很了解可以看看我前面写委托的文章 事件基于委托,是一种功能受限的委托,为委托提供了一种发布/订阅机制 使用委托时, ...
- Java API —— ArrayList类 & Vector类 & LinkList类
1.ArrayList类 1)ArrayList类概述 · 底层数据结构是数组,查询快,增删慢 · 线程不安全,效率高 2)ArrayList案例 ...
- 转载:C++ vector 类学习笔记
声明:本文转载自http://blog.csdn.net/whz_zb/article/details/6827999 vector简介 vector是STL中最常见的容器,它是一种顺序容器,支持随机 ...
- Java Vector 类
Vector类实现了一个动态数组.和ArrayList和相似,但是两者是不同的: Vector是同步访问的. Vector包含了许多传统的方法,这些方法不属于集合框架. Vector主要用在事先不知道 ...
随机推荐
- R class of subset of matrix and data.frame
a = matrix( c(2, 4, 3, 1, 5, 7), # the data elements nrow=2, # number of rows ...
- 题解——洛谷P1962 斐波那契数列(矩阵乘法)
矩阵乘法加速线性递推的典型 大概套路就是先构造一个矩阵\( F \)使得另一初始矩阵\( A \)乘以\( F^{x} \)能够得出第n项 跑的飞快 虽然我也不知道那个矩阵要怎么构造 或许就像我使用了 ...
- 深度学习课程笔记(八)GAN 公式推导
深度学习课程笔记(八)GAN 公式推导 2018-07-10 16:15:07
- (zhuan) LSTM Neural Network for Time Series Prediction
LSTM Neural Network for Time Series Prediction Wed 21st Dec 2016 Neural Networks these days are the ...
- Execl矩阵如何转化成Pajek的net文件
在科研中我们有时会把把execl矩阵利用Ucinet.Pajek等可视化软件进行画图,而想要把execl矩阵转化为 Pajek可识别的文件-->net文件令很多初学者头疼不已,本文将做详细介绍. ...
- _event_phase
EventId 事件ID Phase 阶段ID,从1开始 StopGUID 击杀生物或摧毁物体当前阶段结束,,正数为生物,负数为物体
- Easyui使用心得(1)--DateGrid表格
最近一直在用easyui这个控件,有一点心得,在这里和大家分享一下,也是对自己工作的一个小小的总结,希望可以形成一个完整的Easyui的笔记体系,可以方便更多的人 因为自己也是在摸索中前进,难免有遗漏 ...
- 算法时间复杂度的表示法O(n²)、O(n)、O(1)、O(nlogn)等是什么意思?
Java中 Set 和 List 集合 的contains()方法,检查数组链表中是否包含某元素检查数组链表中是否包含某元素,使用 Set 而不使用 List 的原因是效率问题, 前者的 set ...
- Centos7:查看某个端口被哪个进程占用
查看端口被哪个进程使用的命令 netstat -lnp | grep 参考: https://blog.csdn.net/u010886217/article/details/83626236 htt ...
- PHP中封装Redis购物车功能
<?php // 服务层 namespace Common\Service; use Vendor\Func\Red; class CartService extends CommonServi ...