重识ArrayList(二)

358 阅读2分钟

前言

在上一篇重识ArrayList(一)中,我们说到了arraylist线程不安全的原因,那么线程有没有其他的工具类可以替代ArrayList呢?

有以下三种方式:

  • Vector
  • Collections.synchronizedList()
  • CopyOnWriteArrayList

首先第一个是Vector当然不用说了,在所有的方法里面都加了把锁,效率自然比不过arraylist,基本上已经被淘汰。
第二个Collections.synchronizedList(list)来包装arraylist,不过和Vector一样,方法加锁,效率也不高。
第三种就是使用CopyOnWriteArrayList前,在说CopyOnWriteArrayList之前,我们先说下 CopyOnWrite 的思想

写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

这段话是从网上摘抄过来的,其实简单来说就是读的时候不加锁,写的时候加锁并复制一份数组出来操作,我们来看下源码:

    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 E get(int index) {
       return get(getArray(), index);
   }    
   private E get(Object[] a, int index) {
       return (E) a[index];
   }

从源码可以看出,读的时候加了可重入锁,保证了多线程场景下,同时只会有一个线程复制数组,而在读的时候没有加锁,直接返回数组中的数据。这样就保证了线程安全。
这样的话,CopyOnWriteArrayList的缺点也就很显然了

  • 耗费内存(copy一份数组,如果数组比较大的话,自然很耗费内存)
  • 保证最终一致性,但是没办法满足实时性要求(比如两个线程,线程A正在读取CopyOnWriteArrayList的数据,还没读完,但是线程B此时把数据情况了,由于copy数组需要时间,线程A可能还会读到被删掉的数据)