Jdk1.6 JUC源码解析(23)-CopyOnWriteArrayList、CopyOnWriteArraySet
作者:大飞
功能简介:
- CopyOnWriteArrayList是一种线程安全的ArrayList。顾名思义,有写操作时,就会copy一个新的内部数组出来替换掉旧的数组。这样做的好处是,遍历操作不用加锁了,但是遍历的数组不会感知即时变更,只是一个快照。在某些场景下,这要比不用CopyOnWrite,读写都加锁的实现方式要高效一些。CopyOnWriteArrayList一般使用在读多写少的场景。
- CopyOnWriteArraySet由内部的一个CopyOnWriteArrayList来代理实现。
源码分析:
- 先看下CopyOnWriteArrayList的内部结构:
public class CopyOnWriteArrayList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable { private static final long serialVersionUID = 8673264195747942595L; /** The lock protecting all mutators */ transient final ReentrantLock lock = new ReentrantLock(); /** The array, accessed only via getArray/setArray. */ private volatile transient Object[] array; /** * Gets the array. Non-private so as to also be accessible * from CopyOnWriteArraySet class. */ final Object[] getArray() { return array; } /** * Sets the array. */ final void setArray(Object[] a) { array = a; }
内部结构一目了然,一个锁,一个数组。注意数组由volatile修饰。内部数组只能通过getArray和setArray来操作。
- 继续看下CopyOnWriteArrayList的"写"系列方法:
public E set(int index, E element) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); Object oldValue = elements[index]; //新值和旧值做对比。 if (oldValue != element) { //如果不相等,需要从旧数组拷贝一份新数组,然后设置新值,设置为内部数组。 int len = elements.length; Object[] newElements = Arrays.copyOf(elements, len); newElements[index] = element; setArray(newElements); } else { //这里保证volatile写的语义。 setArray(elements); } return (E)oldValue; } finally { lock.unlock(); } }
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(); } }
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(); } }
public E remove(int index) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; Object oldValue = elements[index]; int numMoved = len - index - 1; if (numMoved == 0)//如果要删除的是数组末尾的元素,直接拷贝,同时减少一个单位的长度。然后设置为内部数组。 setArray(Arrays.copyOf(elements, len - 1)); else {//如果不是删除数组末尾的元素,分两段拷贝,然后设置为内部数组。 Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return (E)oldValue; } finally { lock.unlock(); } }
public boolean remove(Object o) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (len != 0) { // 假设数组中存在o,那么删除后的长度是len - 1,所以先处理len - 1个元素 int newlen = len - 1; // 建立一个新数组,长度为len - 1 Object[] newElements = new Object[newlen]; for (int i = 0; i < newlen; ++i) { if (eq(o, elements[i])) { // 发现了目标元素,跳过这个元素拷贝剩下的元素到新数组。 for (int k = i + 1; k < len; ++k) newElements[k-1] = elements[k]; setArray(newElements); return true; } else newElements[i] = elements[i]; } // 如果到这里的话,说明前len - 1个元素中都没找到o,那么看看最后一个元素是否为o。 if (eq(o, elements[newlen])) { //如果是,直接设置新数组就可以了,因为最后一个元素不在新数组里。 setArray(newElements); return true; } } return false; } finally { lock.unlock(); } }
可见,所有的写操作都会拷贝另外一份内部数组出来,然后替换之前的内部数组,这里也会存在一个内存占用的问题,使用的时候应当注意。其他的写操作都很容易看懂,这里不罗嗦了。
- 读操作很简答:
public E get(int index) { return (E)(getArray()[index]); }
- 再看下迭代器相关:
public Iterator<E> iterator() { return new COWIterator<E>(getArray(), 0); } public ListIterator<E> listIterator() { return new COWIterator<E>(getArray(), 0); } private static class COWIterator<E> implements ListIterator<E> { /** Snapshot of the array **/ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; } public boolean hasNext() { return cursor < snapshot.length; } public boolean hasPrevious() { return cursor > 0; } public E next() { if (! hasNext()) throw new NoSuchElementException(); return (E) snapshot[cursor++]; } public E previous() { if (! hasPrevious()) throw new NoSuchElementException(); return (E) snapshot[--cursor]; } public int nextIndex() { return cursor; } public int previousIndex() { return cursor-1; } public void remove() { throw new UnsupportedOperationException(); } public void set(E e) { throw new UnsupportedOperationException(); } public void add(E e) { throw new UnsupportedOperationException(); } }
注意创建迭代器的时候将当时的array赋予迭代器内部的域,相当于是获取了一个快照,所以遍历的时候不需要加锁,但不支持迭代器上的写操作(写方法都会抛出异常)。
- 没有涉及到的部分,代码都比较容易理解,这里不说了。最后看下CopyOnWriteArraySet,代码很简单:
public class CopyOnWriteArraySet<E> extends AbstractSet<E> implements java.io.Serializable { private static final long serialVersionUID = 5457747651344034263L; private final CopyOnWriteArrayList<E> al; public CopyOnWriteArraySet() { al = new CopyOnWriteArrayList<E>(); } public CopyOnWriteArraySet(Collection<? extends E> c) { al = new CopyOnWriteArrayList<E>(); al.addAllAbsent(c); } public int size() { return al.size(); } public boolean isEmpty() { return al.isEmpty(); } public boolean contains(Object o) { return al.contains(o); } public Object[] toArray() { return al.toArray(); } public <T> T[] toArray(T[] a) { return al.toArray(a); } public void clear() { al.clear(); } public boolean remove(Object o) { return al.remove(o); } public boolean add(E e) { return al.addIfAbsent(e); } ...
内部一个CopyOnWriteArrayList,方法都由内部的CopyOnWriteArrayList来实现。
ok,代码解析完毕!
相关推荐
aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-15.8.0-jdk1.6aspose-words-...
2部分: jdk-1.6-windows-64-01 jdk-1.6-windows-64-02
1. 解压缩"java-jdk1.6-jdk-6u45-windows-x64.zip"文件,这将释放出"jdk-6u45-windows-x64.exe"可执行文件。 2. 双击运行"jdk-6u45-windows-x64.exe",安装向导会引导你完成安装过程。通常,你需要选择安装路径,...
下载的压缩包文件"jdk-6u45-windows-x64(1.6 64).exe"是Windows 64位系统的安装程序。安装过程中,用户需要选择安装路径,并设置环境变量,包括`JAVA_HOME`指向JDK的安装目录,`PATH`添加JDK的bin目录,确保系统可以...
标题中的“jdk1.6集成jjwt的问题”指的是在Java Development Kit (JDK) 版本1.6的环境下,尝试整合JSON Web Token (JWT) 库jjwt时遇到的挑战。JWT是一种开放标准(RFC 7519),用于在各方之间安全地传输信息作为 ...
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
标题中的"jdk-jdk1.6.0.24-windows-i586.exe"是一个Java Development Kit(JDK)的安装程序,适用于Windows操作系统且为32位版本。JDK是Oracle公司提供的一个用于开发和运行Java应用程序的软件包。这个特定的版本,...
1.okhttp3.8源码使用jdk1.6重新编译,已集成了okio,在javaweb项目中使用,未在安卓项目中使用 2.okhttp3.8源码使用jdk1.6重新编译_okhttp3.8.0-jdk1.6.jar
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
三部分: jdk-1.6-windows-32-1 jdk-1.6-windows-32-2 jdk-1.6-windows-32-3
三部分: jdk-1.6-linux-64-1 jdk-1.6-linux-64-2 jdk-1.6-linux-64-3
### JDK1.6安装及与JDK-1.5版本共存 #### 一、前言 随着软件开发环境的变化和技术的进步,不同的项目可能需要不同的Java版本来支持其运行。例如,在某些特定环境下,可能既需要使用JDK1.5(Java Development Kit ...
logback-cfca-jdk1.6-3.1.0.0.jar
- 这可能是ZXing库的完整源码包,专门针对JDK1.6编译,包含了所有必要的源文件和资源,供开发者进行更深度的定制和集成。 总之,ZXing库是一个强大的条形码和二维码工具,这个特别适配JDK1.6的版本为那些仍在使用...
这个压缩包文件"jdk-6u45-linux-x64.zip"包含的是JDK 1.6.0_45(也被称为6u45或1.6u45)的64位Linux版本。JDK 1.6是Java平台标准版的一个重要版本,它提供了许多功能和性能改进,是许多企业级应用的基础。 JDK 1.6u...
jdk-1.6-linux-32-1 jdk-1.6-linux-32-2 jdk-1.6-linux-32-3
压缩包中的文件`jdk-6u45-windows-i586.exe`是JDK 1.6更新45的Windows 32位安装程序。安装步骤通常包括: 1. 下载并运行安装程序。 2. 遵循安装向导的提示,选择安装路径和组件。 3. 设置环境变量,包括`JAVA_HOME`...
java环境搭建 jdk6(包含jre)64位 jdk-6u45-windows-x64
Linux64位环境下的jdk6安装包:jdk-6u45-linux-x64.bin。 由于积分无法修改,现提供网盘下载地址: https://pan.baidu.com/s/1BE55ImTxZTQO6T22051P2g 提取码:5wvm
Java编程开发工具包,最新版本,很好用,经典