CopyOnWriteArrayList 解读

来源:互联网 发布:移动售楼软件 编辑:程序博客网 时间:2024/06/05 14:59

一、 核心思想:

CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。

二、类图预览:


 方法基本分为CopyOnWriteArrayList、indexOf、contains、get、set、add、remove、addIfAbsent和iterator几类:

1、CopyOnWriteArrayList  构造方法:

基本使用Arrays.copyOf 方法,将参数的集合类设置到array属性上。

2、indexOf方法:

 简单的通过循环,对比找到所在的位置,核心代码:

Java代码  收藏代码
  1. for (int i = index; i < fence; i++)  
  2.                if (o.equals(elements[i]))  
  3.                    return i;  

 

值得注意有两点,一是支持NULL对象、二是lastIndexOf从后面往前,提高性能

3、 contains方法:

该方法使用indexOf方法,避免代码重复。containsAll方法也是简单的循环判断是否包含单个元素。

4、get方法:

直接返回对应下标元素

5、set方法:

 

Java代码  收藏代码
  1. public E set(int index, E element) {  
  2.       final ReentrantLock lock = this.lock;  
  3.       lock.lock();  
  4.       try {  
  5.           Object[] elements = getArray();  
  6.           E oldValue = get(elements, index);  
  7.   
  8.           if (oldValue != element) {  
  9.               int len = elements.length;  
  10.               Object[] newElements = Arrays.copyOf(elements, len);  
  11.               newElements[index] = element;  
  12.               setArray(newElements);  
  13.           } else {  
  14.               // Not quite a no-op; ensures volatile write semantics  
  15.               setArray(elements);  
  16.           }  
  17.           return oldValue;  
  18.       } finally {  
  19.           lock.unlock();  
  20.       }  
  21.   }  

 

 

可以看到该法使用ReentrantLock锁, Arrays.copyOf创建一个新的数组是核心思想体现,oldValue != element这个判断更是尽可能的提高性能的努力。

而在esle里面,明明没有任何修改,为什么还要条用set方法,并且在addAllAbsent 方法里面有没有使用,以及那句注释(Not quite a no-op; ensures volatile write semantics),有几封邮件讨论这个问题。

大意是说:为了确保 voliatile 的语义,任何一个读操作都应该是写操作的结构,所以尽管写操作没有改变数据,还是调用set方法,当然这仅仅是语义的说明,去掉也是可以的。而对于 addIfAbsent方法为什么没有使用set方法,那是因为该方法本身的语义就是写或者不写,不写故不需要保持语义。

参考如下:

 

http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006886.html

http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006887.html

http://cs.oswego.edu/pipermail/concurrency-interest/2010-February/006888.html

http://en.usenet.digipedia.org/thread/13652/1242/

 

6、add方法:

Java代码  收藏代码
  1. public boolean add(E e) {  
  2.        final ReentrantLock lock = this.lock;  
  3.        lock.lock();  
  4.        try {  
  5.            Object[] elements = getArray();  
  6.            int len = elements.length;  
  7.            Object[] newElements = Arrays.copyOf(elements, len + 1);  
  8.            newElements[len] = e;  
  9.            setArray(newElements);  
  10.            return true;  
  11.        } finally {  
  12.            lock.unlock();  
  13.        }  
  14.    }  

 

 同样很简单,遵循,使用锁,Arrays.copyOf copy新数组、新增一个元素、set回去步骤。

另外一个重载的指定位置add元素的核心代码如下:

 

Java代码  收藏代码
  1. newElements = new Object[len + 1];  
  2. System.arraycopy(elements, 0, newElements, 0, index);  
  3. System.arraycopy(elements, index, newElements, index + 1,  numMoved);  

主要使用System.arraycopy方法copy到一个新的数组

 

 7、remove方法:

Java代码  收藏代码
  1. public E remove(int index) {  
  2.         final ReentrantLock lock = this.lock;  
  3.         lock.lock();  
  4.         try {  
  5.             Object[] elements = getArray();  
  6.             int len = elements.length;  
  7.             E oldValue = get(elements, index);  
  8.             int numMoved = len - index - 1;  
  9.             if (numMoved == 0)  
  10.                 setArray(Arrays.copyOf(elements, len - 1));  
  11.             else {  
  12.                 Object[] newElements = new Object[len - 1];  
  13.                 System.arraycopy(elements, 0, newElements, 0, index);  
  14.                 System.arraycopy(elements, index + 1, newElements, index,  
  15.                                  numMoved);  
  16.                 setArray(newElements);  
  17.             }  
  18.             return oldValue;  
  19.         } finally {  
  20.             lock.unlock();  
  21.         }  
  22.     }  

 

同样很简单,使用 System.arraycopy、Arrays.copyOf移动元素

移除指定元素方法的核心代码:通过双重循环,比较移动。

Java代码  收藏代码
  1. for (int i = 0; i < newlen; ++i) {  
  2.    if (eq(o, elements[i])) {  
  3.        // found one;  copy remaining and exit  
  4.        for (int k = i + 1; k < len; ++k)  
  5.            newElements[k-1] = elements[k];  
  6.        setArray(newElements);  
  7.        return true;  
  8.    } else  
  9.        newElements[i] = elements[i];  

 移除指定集合内方法核心代码:

Java代码  收藏代码
  1. for (int i = 0; i < len; ++i) {  
  2.     Object element = elements[i];  
  3.     if (!c.contains(element))  
  4.         temp[newlen++] = element;  
  5. }  
  6. if (newlen != len) {  
  7.     setArray(Arrays.copyOf(temp, newlen));  
  8.     return true;  
  9. }  

8、addIfAbsent  方法:

Java代码  收藏代码
  1. public boolean addIfAbsent(E e) {  
  2.        final ReentrantLock lock = this.lock;  
  3.        lock.lock();  
  4.        try {  
  5.            // Copy while checking if already present.  
  6.            // This wins in the most common case where it is not present  
  7.            Object[] elements = getArray();  
  8.            int len = elements.length;  
  9.            Object[] newElements = new Object[len + 1];  
  10.            for (int i = 0; i < len; ++i) {  
  11.                if (eq(e, elements[i]))  
  12.                    return false// exit, throwing away copy  
  13.                else  
  14.                    newElements[i] = elements[i];  
  15.            }  
  16.            newElements[len] = e;  
  17.            setArray(newElements);  
  18.            return true;  
  19.        } finally {  
  20.            lock.unlock();  
  21.        }  
  22.    }  

 

这里可以看到没有又相同的元素之间return了,没有调用set方法;

9、retainAll 方法:

Java代码  收藏代码
  1. Object[] temp = new Object[len];  
  2. for (int i = 0; i < len; ++i) {  
  3.     Object element = elements[i];  
  4.     if (c.contains(element))  
  5.         temp[newlen++] = element;  
  6. }   

基本是removeAll的翻版,只是 if (c.contains(element)) 这个是否定罢了。

10、writeObject、readObject方法:

Java代码  收藏代码
  1. private void writeObject(java.io.ObjectOutputStream s)  
  2.        throws java.io.IOException{  
  3.        s.defaultWriteObject();  
  4.        Object[] elements = getArray();  
  5.        // Write out array length  
  6.        s.writeInt(elements.length);  
  7.        // Write out all elements in the proper order.  
  8.        for (Object element : elements)  
  9.            s.writeObject(element);  
  10.    }  
  11.   
  12.    private void readObject(java.io.ObjectInputStream s)  
  13.        throws java.io.IOException, ClassNotFoundException {  
  14.        s.defaultReadObject();  
  15.        // bind to new lock  
  16.        resetLock();  
  17.        // Read in array length and allocate array  
  18.        int len = s.readInt();  
  19.        Object[] elements = new Object[len];  
  20.   
  21.        // Read in all elements in the proper order.  
  22.        for (int i = 0; i < len; i++)  
  23.            elements[i] = s.readObject();  
  24.        setArray(elements);  
  25.    }  

 

虽然CopyOnWriteArrayList 类实现了 序列化接口,但是变量数组确有transient关键字通过实现这两个方法。将快照序列化

11、iterator  方法:

 

Java代码  收藏代码
  1. public void remove() {  
  2.             throw new UnsupportedOperationException();  
  3.         }  

 

 

针对iterator使用了一个叫COWIterator的阉割版迭代器,因为不支持写操作 ,如上面add、set、remove都会跑出异常,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全。

 

 

综上:

 

在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过Arrays.copyof()来生成一份新的数组,然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象,并且加锁。

 

读操作是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞。

 

因为每次使用CopyOnWriteArrayList.add都要引起数组拷贝, 所以应该避免在循环中使用CopyOnWriteArrayList.add。可以在初始化完成后设置到CopyOnWriteArrayList中,或者使用CopyOnWriteArrayList.addAll方法

 

 

CopyOnWriteArrayList采用“写入时复制”策略,对容器的写操作将导致的容器中基本数组的复制,性能开销较大。所以在有写操作的情况下,CopyOnWriteArrayList性能不佳,而且如果容器容量较大的话容易造成溢出。

0 0
原创粉丝点击