一、 核心思想:
CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的集合,在新的集合上面修改,然后将新集合赋值给旧的引用,并通过volatile 保证其可见性,当然写操作的锁是必不可少的了。
二、类图预览:
方法基本分为CopyOnWriteArrayList、indexOf、contains、get、set、add、remove、addIfAbsent和iterator几类:
1、CopyOnWriteArrayList 构造方法:
基本使用Arrays.copyOf 方法,将参数的集合类设置到array属性上。
2、indexOf方法:
简单的通过循环,对比找到所在的位置,核心代码:
1. for (int
2. if
3. return
值得注意有两点,一是支持NULL对象、二是lastIndexOf从后面往前,提高性能
3、 contains方法:
该方法使用indexOf方法,避免代码重复。containsAll方法也是简单的循环判断是否包含单个元素。
4、get方法:
直接返回对应下标元素
5、set方法:
1. public E set(int
2. final ReentrantLock lock = this.lock;
3. lock.lock();
4. try
5. Object[] elements = getArray();
6. E oldValue = get(elements, index);
7.
8. if
9. int
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
18. finally
19. lock.unlock();
20. }
21. }
可以看到该法使用ReentrantLock锁, Arrays.copyOf创建一个新的数组是核心思想体现,oldValue != element这个判断更是尽可能的提高性能的努力。
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方法:
1. public boolean
2. final ReentrantLock lock = this.lock;
3. lock.lock();
4. try
5. Object[] elements = getArray();
6. int
7. 1);
8. newElements[len] = e;
9. setArray(newElements);
10. return true;
11. finally
12. lock.unlock();
13. }
14. }
同样很简单,遵循,使用锁,Arrays.copyOf copy新数组、新增一个元素、set回去步骤。
另外一个重载的指定位置add元素的核心代码如下:
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方法:
1. public E remove(int
2. final ReentrantLock lock = this.lock;
3. lock.lock();
4. try
5. Object[] elements = getArray();
6. int
7. E oldValue = get(elements, index);
8. int numMoved = len - index - 1;
9. if (numMoved == 0)
10. 1));
11. else
12. new Object[len - 1];
13. 0, newElements, 0, index);
14. 1, newElements, index,
15. numMoved);
16. setArray(newElements);
17. }
18. return
19. finally
20. lock.unlock();
21. }
22. }
同样很简单,使用 System.arraycopy、Arrays.copyOf移动元素
移除指定元素方法的核心代码:通过双重循环,比较移动。
1. for (int i = 0; i < newlen; ++i) {
2. if
3. // found one; copy remaining and exit
4. for (int k = i + 1; k < len; ++k)
5. 1] = elements[k];
6. setArray(newElements);
7. return true;
8. else
9. newElements[i] = elements[i];
内方法核心代码:
1. for (int i = 0; i < len; ++i) {
2. Object element = elements[i];
3. if
4. temp[newlen++] = element;
5. }
6. if
7. setArray(Arrays.copyOf(temp, newlen));
8. return true;
9. }
8、addIfAbsent 方法:
1. public boolean
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
9. new Object[len + 1];
10. for (int i = 0; i < len; ++i) {
11. if
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 方法:
1. Object[] temp = new
2. for (int i = 0; i < len; ++i) {
3. Object element = elements[i];
4. if
5. temp[newlen++] = element;
6. }
基本是removeAll的翻版,只是 if (c.contains(element)) 这个是否定罢了。
10、writeObject、readObject方法:
1. private void
2. throws
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
9. s.writeObject(element);
10. }
11.
12. private void
13. throws
14. s.defaultReadObject();
15. // bind to new lock
16. resetLock();
17. // Read in array length and allocate array
18. int
19. new
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. }
transient关键字通过实现这两个方法。将快照序列化
11、iterator 方法:
1. public void
2. throw new
3. }
针对iterator使用了一个叫COWIterator的阉割版迭代器,因为不支持写操作 ,如上面add、set、remove都会跑出异常,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全。
综上:
在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过Arrays.copyof()来生成一份新的数组,然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象,并且加锁。
读操作是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞。
因为每次使用CopyOnWriteArrayList.add都要引起数组拷贝, 所以应该避免在循环中使用CopyOnWriteArrayList.add。可以在初始化完成后设置到CopyOnWriteArrayList中,或者使用CopyOnWriteArrayList.addAll方法
CopyOnWriteArrayList采用“写入时复制”策略,对容器的写操作将导致的容器中基本数组的复制,性能开销较大。所以在有写操作的情况下,CopyOnWriteArrayList性能不佳,而且如果容器容量较大的话容易造成溢出。