JAVA集合源码分析——Vector

一、Vector概述

1)Vector是一个位于java.util包中线程安全的集合
2)底层数据结构是类似ArrayList一样的可变的数组
3)Vector的数组变化和minCapacity和CapacityIncrement这两个变量有关
4)Vector会发生fail-fast(快速失败)

二、Vector继承层次和实现接口

1)继承层次和实现接口
JAVA集合源码分析——Vector_第1张图片说明:Vector继承的层次和实现的接口基本和ArrayList一样,这里就不在讲解各接口的作用和继承关系,如有不清的可以去看我的ArryList集合源码分析

三、Vector源码(重点方法分析)

1)Vector底层成员
JAVA集合源码分析——Vector_第2张图片说明:
elementCount:Vector中的元素个数
capacityIncrement:Vector数组增长时每次要增加多少,如果为0则增长为原来的2倍
elementData:Vector底层维护的可变对象数组

2)构造方法解析

无参构造函数:
JAVA集合源码分析——Vector_第3张图片
说明:Vector默认给对象数组赋予长度为10,且空参构造函数底层调用初始化initCapacity的构造函数

初始化capacity的构造函数:
Vector初始化initCapacity的构造函数说明:初始化capacity的构造函数,底层会调用初始化initCapacity和capaciryIncrement参数的构造函数,且会把用户指定的initCapacity赋予capacity

同时初始化initialCapacity和capacityIncrement参数的构造函数:
JAVA集合源码分析——Vector_第4张图片
总结:常见的Vector的构造函数都是调用同时初始化initialCapacity和capacityIncrement参数的构造函数,而这个构造函数就是给以上两个变量赋值,要么是指定的或者是默认的

3)ensureCapacity(int mincapacity)

 * @param minCapacity the desired minimum capacity
 */
//用于判断当Vector添加元素时Vector的数组长度是否足够
public synchronized void ensureCapacity(int minCapacity) {
    if (minCapacity > 0) {//校验minCapacity的合法性
        modCount++;
        ensureCapacityHelper(minCapacity);//真正用于判断容量是否足够的方法
    }
}

分析:ensureCapacityHelper(minCapacity)

 * This implements the unsynchronized semantics of ensureCapacity.
 * Synchronized methods in this class can internally call this
 * method for ensuring capacity without incurring the cost of an
 * extra synchronization.
 *
 * @see #ensureCapacity(int)
 */
 //真正用于判断Vector容量是否足够的方法
private void ensureCapacityHelper(int minCapacity) {
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果实际需要的最小容量大于目前Vector的实际容量
        grow(minCapacity);//扩展Vector的容量
}

分析:grow(minCapacity)

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//保存元素数组长度
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?capacityIncrement是否为0,如果为0就增长原来容量的2倍,否则就按指定的capacityIncrement来增长数组长度
                                     capacityIncrement : oldCapacity);
    if (newCapacity - minCapacity < 0)//如果经过一次扩展后的数组长度仍不满足最小容量要求
        newCapacity = minCapacity;//数组长度一次扩展为minCapacity
    if (newCapacity - MAX_ARRAY_SIZE > 0)//如果扩展后的数组容量超过了最大容量
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);//把初始数组的数据复制到新增的数组中
}

小结:Vector底层是维护者一个可变的对象数组,其增长规则是依据minCapacity和capacityIncrment这两个变量值,默认增长为原来2倍,其实原理和arryList差不多。

4)add(E e);

 * Appends the specified element to the end of this Vector.
 *
 * @param e element to be appended to this Vector
 * @return {@code true} (as specified by {@link Collection#add})
 * @since 1.2
 */
public synchronized boolean add(E e) {//Vector之所以安全,原来是在方法上加了对象锁
    modCount++;
    ensureCapacityHelper(elementCount + 1);//每次加元素前判断数组容量是否足够,具体分析看上面的ensureCapacityHelper()方法
    elementData[elementCount++] = e;//添加元素
    return true;
}

四、fail-fast和fail-safe区别

简单的来说:在java.util下的集合都是发生fail-fast,而在java.util.concurrent下的发生的都是fail-safe。

1)fail-fast

快速失败,例如在arrayList中使用迭代器遍历时,有另外的线程对arrayList的存储数组进行了改变,比如add、delete、等使之发生了结构上的改变,

所以Iterator就会快速报一个java.util.ConcurrentModificationException 异常(并发修改异常),这就是快速失败。

2)fail-safe

安全失败,在java.util.concurrent下的类,都是线程安全的类,他们在迭代的过程中,如果有线程进行结构的改变,不会报异常,而是正常遍历,这就是安全失败。

3)为什么在java.util.concurrent包下对集合有结构的改变,却不会报异常?

在concurrent下的集合类增加元素的时候使用Arrays.copyOf()来拷贝副本,在副本上增加元素,如果有其他线程在此改变了集合的结构,那也是在副本上的改变,而不是影响到原集合,

迭代器还是照常遍历,遍历完之后,改变原引用指向副本,所以总的一句话就是如果在次包下的类进行增加删除,就会出现一个副本。所以能防止fail-fast,这种机制并不会出错,所以我们叫这种现象为fail-safe。

4)vector也是线程安全的,为什么是fail-fast呢?

这里搞清楚一个问题,并不是说线程安全的集合就不会报fail-fast,而是报fail-safe,你得搞清楚前面所说答案的原理,出现fail-safe是因为他们在实现增删的底层机制不一样,就像上面说的,

会有一个副本,而像arrayList、linekdList、verctor等,他们底层就是对着真正的引用进行操作,所以才会发生异常。

5)既然是线程安全的,为什么在迭代的时候,还会有别的线程来改变其集合的结构呢(也就是对其删除和增加等操作)?

首先,我们迭代的时候,根本就没用到集合中的删除、增加,查询的操作,就拿vector来说,我们都没有用那些加锁的方法,

也就是方法锁放在那没人拿,在迭代的过程中,有人拿了那把锁,我们也没有办法,因为那把锁就放在那边。
    
fast-fail(快速失败)运行程序举例
   JAVA集合源码分析——Vector_第5张图片JAVA集合源码分析——Vector_第6张图片fail-fase运行程序举例:
   JAVA集合源码分析——Vector_第7张图片JAVA集合源码分析——Vector_第8张图片

五、总结

1)vector实现线程安全的方法是在每个操作方法上加锁,这些锁并不是必须要的,在实际开发中,一般都市通过锁一系列的操作来实现线程安全,也就是说将需要同步的资源放一起加锁来保证线程安全

2)如果多个Thread并发执行一个已经加锁的方法,但是在该方法中,又有vector的存在,vector本身实现中已经加锁了,那么相当于锁上又加锁,会造成额外的开销,

3)就如上面第三个问题所说的,vector还有fail-fast的问题,也就是说它也无法保证遍历安全,在遍历时又得额外加锁,又是额外的开销,还不如直接用arrayList,然后再加锁呢。

你可能感兴趣的:(JAVA集合源码分析)