CopyOnWriteArrayList接口详解

  转载地址:http://my.oschina.net/jielucky/blog/167198

一。CopyOnWriteArrayList是ArrayList 的一个线程安全的变体,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。

     这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法 有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set和add)不受支持。这些方法将抛出UnsupportedOperationException。允许使用所有元素,包括null。

    内存一致性效果:当存在其他并发 collection 时,将对象放入CopyOnWriteArrayList之前的线程中的操作 happen-before 随后通过另一线程从CopyOnWriteArrayList中访问或移除该元素的操作。 

   这种情况一般在多线程操作时,一个线程对list进行修改。一个线程对list进行fore时会出现java.util.ConcurrentModificationException错误。

   下面来看一个列子:两个线程一个线程fore一个线程修改list的值。

package List;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * |CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。
 * @author Administrator
 *
 */
public class ListThreadTest{


	public static class ReadTask implements Runnable{
		List<String> list;
		 
        public ReadTask(List<String> list) {
            this.list = list;
        }
 
        public void run() {
            for (String str : list) {
                System.out.println(str);
            }
        }
		
	}

	 private static class WriteTask implements Runnable {
		 List<String> list;
	        int index;
	 
	        public WriteTask(List<String> list, int index) {
	            this.list = list;
	            this.index = index;
	        }
	 
	        public void run() {
	            list.remove(index);
	            list.add(index, "write_" + index);
	        }
	    }
	 

	    public void run() {
	        final int NUM = 10;
	        List<String> list = new ArrayList<String>();
	        for (int i = 0; i < NUM; i++) {
	            list.add("main_" + i);
	        }
	        ExecutorService executorService = Executors.newFixedThreadPool(NUM);
	        for (int i = 0; i < NUM; i++) {
	            executorService.execute(new ReadTask(list));
	            executorService.execute(new WriteTask(list, i));
	        }
	        executorService.shutdown();
	    }
	 
	    public static void main(String[] args) {
			new ListThreadTest().run();
		}
	
}
运行报错:

main_0
main_1
main_2
main_3
main_4
main_5
main_6
main_7
main_8
write_0
write_1
main_2
main_3
main_4
main_5
main_6
main_7
main_8
main_9
write_0
write_1
Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException
main_2
write_3
write_0
write_1
main_2
write_3
main_4
main_5
main_6
main_7
main_8
main_9
write_0
write_1
main_2
write_3
main_4
write_5
main_6
main_7
main_8
main_9
write_0
write_1
main_2
write_3
main_4
write_5
write_6
write_0
write_1
main_2
write_3
main_4
write_5
write_6
write_7
main_8
main_9
write_0
main_4
write_0
write_0
write_1
write_2
write_3
write_4
write_5
write_6
write_7
write_8
write_9
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	at List.ListThreadTest$ReadTask.run(ListThreadTest.java:23)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
Exception in thread "pool-1-thread-3" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	at List.ListThreadTest$ReadTask.run(ListThreadTest.java:23)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
Exception in thread "pool-1-thread-9" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	at List.ListThreadTest$ReadTask.run(ListThreadTest.java:23)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)
Exception in thread "pool-1-thread-8" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
	at java.util.AbstractList$Itr.next(AbstractList.java:343)
	at List.ListThreadTest$ReadTask.run(ListThreadTest.java:23)
	at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
	at java.lang.Thread.run(Thread.java:619)

从结果中可以看出来。在多线程情况下报错。其原因就是多线程操作结果:那这个种方案不行我们就换个方案。用jdk自带的类CopyOnWriteArrayList来做容器。这个类和ArrayList最大的区别就是add(E) 的时候。容器会自动copy一份出来然后再尾部add(E)。看源码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    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();
    }
    }

用到了Arrays.copyOf 方法。这样导致每次操作的都不是同一个引用。也就不会出现java.util.ConcurrentModificationException错误。
换了种方案看代码:

?
1
2
//      List<String> list = new ArrayList<String>();
         CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
也就把容器list换成了  CopyOnWriteArrayList,其他的没变。 线程里面的list不用改。因为  CopyOnWriteArrayList实现的也是list<E> 接口。看结果:

其结果没报错。
CopyOnWriteArrayList add(E
) 和remove(int index)都是对新的数组进行修改和新增。所以在多线程操作时不会出现java.util.ConcurrentModificationException错误。

所以最后得出结论:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。发生修改时候做copy,新老版本分离,保证读的高性能,适用于以读为主的情况。


二:关于java.util.ConcurrentModificationException发生错误的原因,转载自http://fine36.blog.163.com/blog/static/189251005201258113857343/

内容为:

iterator遍历集合时碰到java.util.ConcurrentModificationException这个异常,

下面以List为例来解释为什么会报java.util.ConcurrentModificationException这个异常,代码如下:

 

Java代码   收藏代码
  1. public static void main(String[] args) {  
  2.  List<String> list = new ArrayList<String>();  
  3.          list.add("1");  
  4.           list.add("2");  
  5.           list.add("3");  
  6.           list.add("4");  
  7.           list.add("5");  
  8.           list.add("6");  
  9.           list.add("7");  
  10.             
  11.  List<String> del = new ArrayList<String>();  
  12.           del.add("5");  
  13.           del.add("6");  
  14.           del.add("7");    
  15.   
  16. <span style="color: #ff0000;">for(String str : list){  
  17.      if(del.contains(str)) {  
  18.             list.remove(str);            
  19. }  
  20.           }</span>  
  21.  }  

 运行这段代码会出现如下异常:

 

Java代码   收藏代码
  1. Exception in thread "main" java.util.ConcurrentModificationException  

 

 

 for(String str : list) 这句话实际上是用到了集合的iterator() 方法

 JDK java.util. AbstractList类中相关源码

Java代码   收藏代码
  1. public Iterator<E> iterator() {  
  2.    return new Itr();  
  3. }  

 

 

java.util. AbstractList的内部类Itr的源码如下:

 

Java代码   收藏代码
  1. private class Itr implements Iterator<E> {  
  2.     /** 
  3.      * Index of element to be returned by subsequent call to next. 
  4.      */  
  5.     int cursor = 0;  
  6.   
  7.     /** 
  8.      * Index of element returned by most recent call to next or 
  9.      * previous.  Reset to -1 if this element is deleted by a call 
  10.      * to remove. 
  11.      */  
  12.     int lastRet = -1;  
  13.   
  14.     /** 
  15.      * The modCount value that the iterator believes that the backing 
  16.      * List should have.  If this expectation is violated, the iterator 
  17.      * has detected concurrent modification. 
  18.      */  
  19.     int expectedModCount = modCount;  
  20.   
  21.     public boolean hasNext() {  
  22.             return cursor != size();  
  23.     }  
  24.   
  25.     public E next() {  
  26.             checkForComodification(); //检测modCount和expectedModCount的值!!  
  27.         try {  
  28.         E next = get(cursor);  
  29.         lastRet = cursor++;  
  30.         return next;  
  31.         } catch (IndexOutOfBoundsException e) {  
  32.         checkForComodification();  
  33.         throw new NoSuchElementException();  
  34.         }  
  35.     }  
  36.   
  37.     public void remove() {  
  38.         if (lastRet == -1)  
  39.         throw new IllegalStateException();  
  40.             checkForComodification();  
  41.   
  42.         try {  
  43.         AbstractList.this.remove(lastRet); //执行remove的操作  
  44.         if (lastRet < cursor)  
  45.             cursor--;  
  46.         lastRet = -1;  
  47.         expectedModCount = modCount; //保证了modCount和expectedModCount的值的一致性,避免抛出ConcurrentModificationException异常  
  48.         } catch (IndexOutOfBoundsException e) {  
  49.         throw new ConcurrentModificationException();  
  50.         }  
  51.     }  
  52.   
  53.     final void checkForComodification() {  
  54.         if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常  
  55.         throw new ConcurrentModificationException();  
  56.     }  
  57.     }  

 

 

再看一下ArrayList 的 remove方法

 

Java代码   收藏代码
  1. public boolean remove(Object o) {  
  2.     if (o == null) {  
  3.             for (int index = 0; index < size; index++)  
  4.         if (elementData[index] == null) {  
  5.             fastRemove(index);  
  6.             return true;  
  7.         }  
  8.     } else {  
  9.         for (int index = 0; index < size; index++)  
  10.         if (o.equals(elementData[index])) {  
  11.             fastRemove(index);  
  12.             return true;  
  13.         }  
  14.         }  
  15.     return false;  
  16.     }  
  17.   
  18.     /* 
  19.      * Private remove method that skips bounds checking and does not 
  20.      * return the value removed. 
  21.      */  
  22.     private void fastRemove(int index) {  
  23.         modCount++; //只是修改了modCount,因此modCount将与expectedModCount的值不一致  
  24.         int numMoved = size - index - 1;  
  25.         if (numMoved > 0)  
  26.             System.arraycopy(elementData, index+1, elementData, index,  
  27.                              numMoved);  
  28.         elementData[--size] = null// Let gc do its work  
  29.     }   
 

 

回过头去看看java.util. AbstractListnext()方法

 

Java代码   收藏代码
  1. public E next() {  
  2.             checkForComodification(); //检测modCount和expectedModCount的值!!  
  3.         try {  
  4.         E next = get(cursor);  
  5.         lastRet = cursor++;  
  6.         return next;  
  7.         } catch (IndexOutOfBoundsException e) {  
  8.         checkForComodification();  
  9.         throw new NoSuchElementException();  
  10.         }  
  11.     }  
  12.    
  13. final void checkForComodification() {  
  14.         if (modCount != expectedModCount) //当modCount和expectedModCount值不相等时,则抛出ConcurrentModificationException异常  
  15.         throw new ConcurrentModificationException();  
  16.     }  
  17.     }  

 

 

现在真相终于大白了,ArrayListremove方法只是修改了modCount的值,并没有修改expectedModCount,导致modCountexpectedModCount的值的不一致性,当next()时则抛出ConcurrentModificationException异常

因此使用Iterator遍历集合时,不要改动被迭代的对象,可以使用 Iterator 本身的方法 remove() 来删除对象,Iterator.remove() 方法会在删除当前迭代对象的同时维护modCountexpectedModCount值的一致性。

 

 

解决办法如下:

(1)  新建一个集合存放要删除的对象,等遍历完后,调用removeAll(Collection<?> c)方法

把上面例子中迭代集合的代码替换成:

 

 

Java代码   收藏代码
  1. List<String> save = new ArrayList<String>();  
  2.             
  3.           for(String str : list)  
  4.           {  
  5.            if(del.contains(str))  
  6.            {  
  7.                save.add(str);  
  8.            }  
  9.           }  
  10.           list.removeAll(save);  
 

 

   (2) 使用Iterator替代增强型for循环:

 

Java代码   收藏代码
  1. Iterator<String> iterator = list.iterator();  
  2.      while(iterator.hasNext()) {  
  3.          String str = iterator.next();  
  4.          if(del.contains(str)) {  
  5.              iterator.remove();  
  6.          }  
  7.      }  

      Iterator.remove()方法保证了modCountexpectedModCount的值的一致性,避免抛出ConcurrentModificationException异常。

 

不过对于在多线程环境下对集合类元素进行迭代修改操作,最好把代码放在一个同步代码块内,这样才能保证modCountexpectedModCount的值的一致性,类似如下:

 

Java代码   收藏代码
  1. Iterator<String> iterator = list.iterator();    
  2. synchronized(synObject) {  
  3.  while(iterator.hasNext()) {    
  4.          String str = iterator.next();    
  5.          if(del.contains(str)) {    
  6.              iterator.remove();    
  7.          }    
  8.      }    
  9. }  
  10.       
 

因为迭代器实现类如:ListItr的next(),previous(),remove(),set(E e),add(E e)这些方法都会调用checkForComodification(),源码:

 

Java代码   收藏代码
  1. final void checkForComodification() {  
  2.         if (modCount != expectedModCount)  
  3.         throw new ConcurrentModificationException();  
  4.     }  
 

 

 

 

 

 

曾经写了下面这段对HashMap进行迭代删除操作的错误的代码:

 

Java代码   收藏代码
  1. Iterator<Integer> iterator = windows.keySet().iterator();  
  2.         while(iterator.hasNext()) {  
  3.             int type = iterator.next();  
  4.             windows.get(type).closeWindow();  
  5.             iterator.remove();  
  6.             windows.remove(type);   //  
  7.         }  

 

 上面的代码也会导致ConcurrentModificationException的发生。罪魁祸首是windows.remove(type);这一句。

根据上面的分析我们知道iterator.remove();会维护modCountexpectedModCount的值的一致性,而windows.remove(type);这句是不会的。其实这句是多余的,上面的代码去掉这句就行了。

iterator.remove()的源码如下:HashIterator类的remove()方法

 

 

Java代码   收藏代码
  1. public void remove() {  
  2.             if (lastEntryReturned == null)  
  3.                 throw new IllegalStateException();  
  4.             if (modCount != expectedModCount)  
  5.                 throw new ConcurrentModificationException();  
  6.             HashMap.this.remove(lastEntryReturned.key);  
  7.             lastEntryReturned = null;  
  8.             expectedModCount = modCount; //保证了这两值的一致性  
  9.         }  

 HashMap.this.remove(lastEntryReturned.key);这句代码说明windows.remove(type);是多余的,因为已经删除了该key对应的value。

windows.remove(type)的源码:

 

 

Java代码   收藏代码
  1. public V remove(Object key) {  
  2.         if (key == null) {  
  3.             return removeNullKey();  
  4.         }  
  5.         int hash = secondaryHash(key.hashCode());  
  6.         HashMapEntry<K, V>[] tab = table;  
  7.         int index = hash & (tab.length - 1);  
  8.         for (HashMapEntry<K, V> e = tab[index], prev = null;  
  9.                 e != null; prev = e, e = e.next) {  
  10.             if (e.hash == hash && key.equals(e.key)) {  
  11.                 if (prev == null) {  
  12.                     tab[index] = e.next;  
  13.                 } else {  
  14.                     prev.next = e.next;  
  15.                 }  
  16.                 modCount++;  
  17.                 size--;  
  18.                 postRemove(e);  
  19.                 return e.value;  
  20.             }  
  21.         }  
  22.         return null;  
  23.     }  

 上面的代码中,由于先调用了iterator.remove();所以再调用HashMap的remove方法时,key就已经为null了,所以会执行:removeNullKey();

方法,removeNullKey()源码:

 

 

Java代码   收藏代码
  1. private V removeNullKey() {  
  2.        HashMapEntry<K, V> e = entryForNullKey;  
  3.        if (e == null) {  
  4.            return null;  
  5.        }  
  6.        entryForNullKey = null;  
  7.        modCount++;  
  8.        size--;  
  9.        postRemove(e);  
  10.        return e.value;  
  11.    }  

 不过不管是执行removeNullKey()还是key != null,如果直接调用HashMap的remove方法,都会导致ConcurrentModificationException

这个异常的发生,因为它对modCount++;没有改变expectedModCount的值,没有维护维护索引的一致性。

 

下面引用一段更专业的解释:

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变,所以当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性


三.CopyOnWriteArrayList API方法的源码实现:
http://caoyaojun1988-163-com.iteye.com/blog/1754686

内容为:

一、 核心思想:

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值