jdk源码分析之CopyOnWriteArrayList
2016-05-25 14:53
495 查看
CopyOnWriteArrayList的原理
CopyOnWriteArrayList的核心思想是利用高并发往往是读多写少的特性,对读操作不加锁,对写操作,先复制一份新的数组,在新的数组上面修改,然后将新数组赋值给旧数组的引用,并通过volatile 保证其可见性,通过Lock保证并发写。底层数据结构
private volatile transient Object[] array; final Object[] getArray() { return array; final void setArray(Object[] a) { array = a; }
底层采用Object数组存储数据
数组使用volatile修饰保证可见性,不读缓存直接读写内存
数组使用private修饰限制访问与,只能通过getter和setter访问
数组使用transient修饰,表示序列化时忽略此字段(自己定制序列化操作)
定制序列化操作
因为Object数组被transient修饰,因此需要CopyOnWriteArrayList类自己制定序列化方案方法writeObject和readObject处理对象的序列化。如果声明该方法,它将会被ObjectOutputStream调用而不是采用默认的序列化方案。ObjectOutputStream使用了反射来寻找是否声明了这两个方法。因为ObjectOutputStream使用getPrivateMethod,所以这些方法不得不被声明为priate以至于供ObjectOutputStream来使用。
在两个方法的开始处,调用了defaultWriteObject()和defaultReadObject()。它们做的是默认的序列化进程,就像写/读所有的non-transient和 non-static字段(但他们不会去做serialVersionUID的检查).通常说来,所有我们想要自己处理的字段都应该声明为transient。这样的话,defaultWriteObject/defaultReadObject便可以专注于其余字段,而我们则可为被transient修饰的字段定制序列化。
/** * Saves the state of the list to a stream (that is, serializes it). */ private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{ s.defaultWriteObject(); Object[] elements = getArray(); // Write out array length s.writeInt(elements.length); // Write out all elements in the proper order. for (Object element : elements) s.writeObject(element); }
先调用s.defaultWriteObject()对非transient修饰的字段进行序列化操作
然后序列化写入数组的长度,再循环写入数组的元素
/** * Reconstitutes the list from a stream (that is, deserializes it). */ private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // bind to new lock resetLock(); // Read in array length and allocate array int len = s.readInt(); Object[] elements = new Object[len]; // Read in all elements in the proper order. for (int i = 0; i < len; i++) elements[i] = s.readObject(); setArray(elements); }
反序列化的时候先调用s.defaultReadObject()恢复没有被transient修饰的字段
然后为反序列化得到的CopyOnWriteArrayList对象创建一把新锁
接着恢复数组的长度,根据数组的长度创建一个Object的数组
然后恢复数组的每一个元素
读操作不加锁
public E get(int index) { return get(getArray(), index); } private E get(Object[] a, int index) { return (E) a[index]; }
读操作是直接通过getArray方法获取Object数组,然后通过下标index直接访问数据。读操作并没有加锁,也没有并发的带来的问题,因为写操作是加锁写数组的副本,写操作成功将副本替换为原数据,这也是写时复制名字的由来。
加锁写副本
public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics setArray(elements); } return oldValue; } finally { lock.unlock(); } }
set方法先通过lock加锁,然后获取index位置的旧数据,供最后方法返回使用
E oldValue = get(elements, index);
接着创建数组的副本,在副本上进行数据的替换
Object[] newElements = Arrays.copyOf(elements, len);
Arrays.copyOf(elements, len)方法将会从elements数组复制len个数据创建一个新的数组返回
然后在新数组上进行数据替换,然后将新数组设置为CopyOnWriteArrayList的底层数组
newElements[index] = element;
setArray(newElements);
最后在finally块里边释放锁
特定位置添加数据
public void add(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; int numMoved = len - index; if (numMoved == 0) newElements = Arrays.copyOf(elements, len + 1); else { newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); } }
添加数据和替换数据类似,先加锁,然后数组下标检查,接着创建数组副本,在副本里边添加数据,将副本设置为CopyOnWriteArrayList的底层数组
相关文章推荐
- 从源码安装Mysql/Percona 5.5
- JDK动态代理VS CgLib
- Ubuntu 安装 JDK 问题
- 浅析Ruby的源代码布局及其编程风格
- 探究在C++程序并发时保护共享数据的问题
- 在ASP.NET 2.0中操作数据之四十八:对SqlDataSource控件使用开放式并发
- 在ASP.NET 2.0中操作数据之二十一:实现开放式并发
- asp.net 抓取网页源码三种实现方法
- Nodejs实战心得之eventproxy模块控制并发
- JS小游戏之仙剑翻牌源码详解
- JS小游戏之宇宙战机源码详解
- jQuery源码分析之jQuery中的循环技巧详解
- 本人自用的global.js库源码分享
- 并发环境下mysql插入检查方案
- java中原码、反码与补码的问题分析
- ASP.NET使用HttpWebRequest读取远程网页源代码
- jdk与jre的区别 很形象,很清晰,通俗易懂
- jdk中String类设计成final的原由
- win7下安装 JDK 基本流程
- jdk环境变量配置