CopyOnWriteArray和Arraylist和Vector

微信公众号:Java成长录

感兴趣可以关注下哦,Java知识点,学习路线规划,Java相关电子书,一起学习呀!。

////////////////////////////////////////////////////////////////////////// 

一,相似点

1,CopyOnWriteArray和Arraylist和Vector都是实现了List<>接口;

2,底层都是由动态数组支持,并都支持 增 删 改 查操作;

以下是部分源码

CopyOnWriteArray:

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

        private transient volatile Object[] array;
}

 Arraylist:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

        transient Object[] elementData; // non-private to simplify nested class access
}

 Vector:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{

        protected Object[] elementData;
}

 

 

二,差异处

1,非线程安全:ArrayList   

      线程安全:Vector,CopyOnWriteArray

线程安全的设计主要体现在以下增删改查方面,所以下面将从源码横向比较下三种集合的不同?

CopyOnWriteArray:

  • 增删改之前先持有一个ReentrantLock lock = new ReentrantLock(),操作完之后释放;
  • 底层是用volatile transient声明的数组 array,可以实现线程间可见性;
  • 读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array;

a,增加元素:

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    //获得重入锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        //复制一个新的数组
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        //插入新值
        newElements[len] = e;
        //将引用指向新的数组
        setArray(newElements);
        return true;
    } finally {
        //释放重入锁
        lock.unlock();
    }
}

   
public void add(int index, E element) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        if (index > len || index < 0)
            throw new IndexOutOfBoundsException("Index: "+index+
                                                ", Size: "+len);
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            newElements = Arrays.copyOf(elements, len + 1);
        else {
            newElements = new Object[len + 1];
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element;
        setArray(newElements);
    } finally {
        lock.unlock();
    }
}

b,删除元素:

public E remove(int index) {
    final ReentrantLock lock = this.lock;
    //开始获得锁
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        E oldValue = get(elements, index);
        int numMoved = len - index - 1;
        if (numMoved == 0)
            //如果删除的元素是最后一个,直接复制该元素前的所有元素到新的数组
            setArray(Arrays.copyOf(elements, len - 1));
        else {
            //创建新的数组
            Object[] newElements = new Object[len - 1];
            //将旧数组index+1开始复制到新数组index开始,复制元素个数numMoved
            System.arraycopy(elements, 0, newElements, 0, index);
            System.arraycopy(elements, index + 1, newElements, index,
                             numMoved);
            setArray(newElements);
        }
        return oldValue;
    } finally {
        //结束释放锁
        lock.unlock();
    }
}

c,修改元素值

public E set(int index, E element) {
    final ReentrantLock lock = this.lock;
    //开始获得锁
    lock.lock();
    try {
        Object[] elements = getArray();
        E oldValue = get(elements, index);

        if (oldValue != element) {
            int len = elements.length;
            //创建新数组
            Object[] newElements = Arrays.copyOf(elements, len);
            //修改元素值
            newElements[index] = element;
            //将引用指向新得数组
            setArray(newElements);
        } else {
            // Not quite a no-op; ensures volatile write semantics
            setArray(elements);
        }
        return oldValue;
    } finally {
        //结束释放锁
        lock.unlock();
    }
}

d,查询元素

//直接获取index对应的元素
public E get(int index) {
    return get(getArray(), index);
}
private E get(Object[] a, int index) {
    return (E) a[index];
}

CopyOnWriteArray小结:增删改的时候获取锁,保证并发安全性,array数组被volatile修饰,线程间可见性,读的时候是没有锁的,读操作过程中如果由增删改操作,那么读操作依然能读,不过读到的可能是旧值,但是不会导致获取不到的情况。

ArrayList:

  • 插入前会判断数组容量是否足够,不够的话会进行扩容,就是新建一个新的数组,然后将老的数据里面的元素复制到新的数组里面
  • 移除元素的时候也涉及到数组中元素的移动,删除指定index位置的元素,然后将index+1至数组最后一个元素往前移动一个格
  • modCount表示增加删除元素操作的次数,主要目的是保证读取的时候不允许对数组在进行插入或删除操作,如果modCount不一致,会报异常ConcurrentModificationException

增加元素:

public boolean add(E e) {
    //进行数组容量判断,不够就扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
 }

 
public void add(int index, E element) {
    //检查是否会越界
    rangeCheckForAdd(index);
    //进行数组容量判断,不够就扩容
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    //将index至数据最后一个元素整体往后移动一格,然后插入新的元素
    System.arraycopy(elementData, index, elementData, index + 1,
                    size - index);
    elementData[index] = element;
    size++;
}

删除元素:

public E remove(int index) {
    //判断是否越界
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;
    //若该元素不是最后一个元素的话,将index+1至数组最后一个元素整体向前移动一格
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
    }

修改元素:

public E set(int index, E element) {
    rangeCheck(index);
    E oldValue = elementData(index);
    elementData[index] = element;
    return oldValue;
}

获取元素:

public E get(int index) {
    rangeCheck(index);
    return elementData(index);
}

E elementData(int index) {
    return (E) elementData[index]; 
}

ArrayList小结:ArrayList底层是数组,支持动态扩容,扩容为原来的1.5倍,由于数组的结构导致,ArrayList查询和修改元素速率很快O(1),但是增加,删除元素O(N)比较慢,需要将元素整体移动去腾出位子,或者去除一个位子。

另外,由于线程不安全问题也是一个问题的源点,ArrayList会越界吗?

首先答案是会越界,是数组都会有越界的可能性,但是不是进行插入前都会判断是否越界并且支持动态扩容吗?

单线程是不会在正常情况下越界的,来看下多线程如何越界的吧。

举个例子:

集合默认长度10,第一次扩容位10+10/2 =15,当集合中已经添加了14个元素时,一个线程率先进入add()方法,在执行ensureCapacityInternal(size + 1)时,发现还可以添加一个元素,故数组没有扩容,但随后该线程被阻塞在此处。接着另一线程进入add()方法,执行ensureCapacityInternal(size + 1),由于前一个线程并没有添加元素,故size依然为14,依然不需要扩容,所以该线程就开始添加元素,使得size++,变为15,数组已经满了。而刚刚阻塞在elementData[size++] = e;语句之前的线程开始执行,它要在集合中添加第16个元素,而数组容量只有15个,所以就发生了数组下标越界异常!

 

Vector

  • 可以在申明一个Vector时候,输入一个capacityIncrement,申明扩容量,不申明默认扩为两倍;
  • 增加删除synchronize关键字修适,保证并发安全性,但是效率会降低;

增加元素:

public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }


public void add(int index, E element) {
        insertElementAt(element, index);
    }

删除元素:

public boolean remove(Object o) {
        return removeElement(o);
    }



 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;
    }

修改元素:

 public synchronized E set(int index, E element) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        E oldValue = elementData(index);
        elementData[index] = element;
        return oldValue;
    }

读取元素:

public synchronized E get(int index) {
        if (index >= elementCount)
            throw new ArrayIndexOutOfBoundsException(index);

        return elementData(index);
    }

Vector小结:可以看到Vector在增删改查方法都用到了synchronized关键字进行了修饰,所以能保证线程安全,但是也会导致效率低下,此集合也是基本属于淘汰的集合啦。

总结:ArrayList,不具有线程安全性,如果想要安全的集合,可以使用其他支持线程安全的集合,或者Collections.synchronizedList生成线程安全集合,也是最常用的List集合;Vector是增删改查方法都加了synchronized关键字来保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章