此文已由作者赵计刚授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
注:在看这篇文章之前,如果对ArrayList底层不清楚的话,建议先去看看ArrayList源码解析。
http://www.cnblogs.com/java-zhao/p/5102342.html
1、对于CopyOnWriteArrayList需要掌握以下几点
创建:CopyOnWriteArrayList()
添加元素:即add(E)方法
获取单个对象:即get(int)方法
删除对象:即remove(E)方法
遍历所有对象:即iterator(),在实际中更常用的是增强型的for循环去做遍历
注:CopyOnWriteArrayList是一个线程安全,读操作时无锁的ArrayList。
2、创建
public CopyOnWriteArrayList()
使用方法:
List<String> list = new CopyOnWriteArrayList<String>();
相关源代码:
private volatile transient Object[] array;//底层数据结构 /** * 获取array */ final Object[] getArray() { return array; } /** * 设置Object[] */ final void setArray(Object[] a) { array = a; } /** * 创建一个CopyOnWriteArrayList * 注意:创建了一个0个元素的数组 */ public CopyOnWriteArrayList() { setArray(new Object[0]); }
注意点:
设置一个容量为0的Object[];ArrayList会创造一个容量为10的Object[]
3、添加元素
public boolean add(E e)
使用方法:
list.add("hello");
源代码:
/** * 在数组末尾添加元素 * 1)获取锁 * 2)上锁 * 3)获取旧数组及其长度 * 4)创建新数组,容量为旧数组长度+1,将旧数组拷贝到新数组 * 5)将要增加的元素加入到新数组的末尾,设置全局array为新数组 */ public boolean add(E e) { final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁 lock.lock();//上锁 try { Object[] elements = getArray();//获取当前的数组 int len = elements.length;//获取当前数组元素 /* * Arrays.copyOf(elements, len + 1)的大致执行流程: * 1)创建新数组,容量为len+1, * 2)将旧数组elements拷贝到新数组, * 3)返回新数组 */ Object[] newElements = Arrays.copyOf(elements, len + 1); newElements[len] = e;//新数组的末尾元素设成e setArray(newElements);//设置全局array为新数组 return true; } finally { lock.unlock();//解锁 } }
注意点:
Arrays.copyOf(T[] original, int newLength)该方法在ArrayList中讲解过
疑问:
在add(E)方法中,为什么要重新定义一个ReentrantLock,而不直接使用那个定义的类变量锁(全局锁)
答:事实上,按照他那样写,即使是在add、remove、set中存在多个引用,最后也是一个实例this.lock,所以不管你在add、remove、set中怎样去从新定义一个ReentrantLock,其实add、remove、set中最后使用的都是同一个锁this.lock,也就是说,同一时刻,add/remove/set只能有一个在运行。这样讲,就是说,下边这段代码完全可以做一个修改。修改前的代码:
public boolean add(E e) { final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁 lock.lock();//上锁
修改后的代码:
public boolean add(E e) { //final ReentrantLock lock = this.lock;//这里为什么不直接用this.lock(即类中已经初始化好的锁)去上锁 this.lock.lock();//上锁
根据以上代码可知,每增加一个新元素,都要进行一次数组的复制消耗,那为什么每次不将数组的元素设大(比如说像ArrayList那样,设置为原来的1.5倍+1),这样就会大大减少因为数组元素复制所带来的消耗?
4、获取元素
public E get(int index)
使用方法:
list.get(0)
源代码:
/** * 根据下标获取元素 * 1)获取数组array * 2)根据索引获取元素 */ public E get(int index) { return (E) (getArray()[index]); }
注意点:
获取不需要加锁
疑问:在《分布式Java应用:基础与实践》一书中作者指出:读操作会发生脏读,为什么?
从类属性部分,我们可以看到array数组是volatile修饰的,也就是当你对volatile进行写操作后,会将写过后的array数组强制刷新到主内存,在读操作中,当你读出数组(即getArray())时,会强制从主内存将array读到工作内存,所以应该不会发生脏读才对呀!!!
补:volatile的介绍见《附2 volatile》,链接如下:
http://www.cnblogs.com/java-zhao/p/5125698.html
免费领取验证码、内容安全、短信发送、直播点播体验包及云服务器等套餐
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 10本最热门科普书免费送!人工智能数学物理获奖经典佳作!