美文网首页
Java 集合框架_Vector(源码解析)

Java 集合框架_Vector(源码解析)

作者: wo883721 | 来源:发表于2017-12-25 14:12 被阅读42次

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的成员属性更加简洁一点。

  1. elementData Object数组用来存储集合中元素。
  2. elementCount表示集合中元素的数量。
  3. capacityIncrement 表示数组每次扩容固定增加的数量。
  4. 还有一个重要属性就是继承自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();
            }
        }
    }
}

相关文章

网友评论

      本文标题:Java 集合框架_Vector(源码解析)

      本文链接:https://www.haomeiwen.com/subject/olchgxtx.html