CopyOnWriteArrayList
引入: 在Java中除了加锁外,还有一种方法可以防止并发修改异常,那就是读写分离技术。 常识: 1)、在Java中"="操作只是将引用和某个对象关联上,如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么它们之间不会发生ConcurrentModificationException异常,它们在虚拟机层面阻塞的,而且速度非常快,几乎不会耗费CPU时间。 2)、Java中两个不同的引用指向同一个对象,当第一个引用指向另一个对象时,第二个引用还将指向原来的对象。 CopyOnWriteArrayList是ArrayList的一个线程安全的变体,底层也是通过数组+锁实现的,且CopyOnWriteArraySet底层是由CopyOnWriteArrayList实现的。 CopyOnWriteArrayList中更改操作如put/set/remove等都是通过对底层数组进行一次新的复制(Arrays.copyOf)来实现的,其每次更改操作都会加入一把锁(ReentrantLock);而其查询操作如size/isEmpty/get等都不会加锁,其中iterator,其使用了COWIterator的地带其,该迭代器不支持更改操作(如add,remove,set),故当获取该迭代器时,引用指向当前数据的对象,不管将来发生什么写操作,都不会再更改该迭代器里的数据对象,所以迭代器是安全的。 备注:当调用subList时,修改父List时,此时再访问子List绝对会发生ConcurrentModificationException;防止,当修改子List时,父List也会跟着更改且不会发生异常。 根据上面的描述,可知读操作(如获取大小,是否为空、获取元素等)不会加锁,但写操作(添加、删除等)会加锁,每次更改操作都会复制数组,这是一个耗时且耗内存的操作,故CopyOnWriteArrayList适合读多写少的场景。
源码:
//锁 transient final ReentrantLock lock = new ReentrantLock(); //用于存储数据的数组 private volatile transient Object[] array; //获取数组的引用 final Object[] getArray() { return array; } //将数组的引用指向新的数组对象 final void setArray(Object[] a) { array = a; } //获取List大小 public int size() { return getArray().length; } //判断List是否为空 public boolean isEmpty() { return size() == 0; } //获取数组的具体下标的元素 private E get(Object[] a, int index) { return (E) a[index]; } //根据下标获取List中的元素 public E get(int index) { return get(getArray(), index); } //替换指定位置的元素,返回旧元素 public E set(int index, E element) { //加锁 final ReentrantLock lock = this.lock; lock.lock(); try { //获取List数组的引用 Object[] elements = getArray(); E oldValue = get(elements, index); if (oldValue != element) { //当与旧元素不同时,需要更新 int len = elements.length; //复制一份新的数组对象 Object[] newElements = Arrays.copyOf(elements, len); //更新旧的元素 newElements[index] = element; //将List中的数组引用指向新的数组对象 setArray(newElements); } else { // Not quite a no-op; ensures volatile write semantics //将List中的数组引用指向新的数组对象 setArray(elements); } return oldValue; } finally { lock.unlock(); } } //往List中添加元素 public boolean add(E e) { //加锁 final ReentrantLock lock = this.lock; lock.lock(); try { //获取List数组的引用 Object[] elements = getArray(); int len = elements.length; //复制一份新的数组对象 Object[] newElements = Arrays.copyOf(elements, len + 1); //将元素添加到新数组中 newElements[len] = e; //将List中的数组引用指向新的数组对象 setArray(newElements); return true; } finally { lock.unlock(); } } //往指定的位置添加元素 public void add(int index, E element) { //加锁 final ReentrantLock lock = this.lock; lock.lock(); try { //获取List数组的引用 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]; //将该位置之前的元素都移动到新数组中,该部分在新数组中位置是从0开始,该部分大小为index System.arraycopy(elements, 0, newElements, 0, index); //将该位置以后包括该位置的元素都移动到新数组中,该部分在新数组中位置是从index+1开始,该部分大小为len - index System.arraycopy(elements, index, newElements, index + 1, numMoved); } //最后在位置插入新元素 newElements[index] = element; //将List中的数组引用指向新的数组对象 setArray(newElements); } finally { lock.unlock(); } } //删除指定位置上的元素返回要删除的元素 public E remove(int index) { //加锁 final ReentrantLock lock = this.lock; lock.lock(); try { //获取List数组的引用 Object[] elements = getArray(); int len = elements.length; //获取该位置的元素 E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) //当是删除的是最后一位时 //将List中的数组引用指向新的数组对象 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); //将List中的数组引用指向新的数组对象 setArray(newElements); } return oldValue; } finally { lock.unlock(); } } //两个对象的比较 private static boolean eq(Object o1, Object o2) { return (o1 == null ? o2 == null : o1.equals(o2)); } //删除指定的元素 public boolean remove(Object o) { //加锁 final ReentrantLock lock = this.lock; lock.lock(); try { //获取List数组的引用 Object[] elements = getArray(); int len = elements.length; if (len != 0) { // Copy while searching for element to remove // This wins in the normal case of element being present int newlen = len - 1; Object[] newElements = new Object[newlen]; //先只比对原数组中的len-1个元素是否是需要删除的元素,若是,则将该元素之后的元素都添加到新数组中,若不是,则只需要将当前原数组元素添加到新数组中 for (int i = 0; i < newlen; ++i) { if (eq(o, elements[i])) { //当找到时,将原数组中的该位置之后的元素都添加到新数组中,结束 // found one; copy remaining and exit for (int k = i + 1; k < len; ++k) newElements[k-1] = elements[k]; //将List中的数组引用指向新的数组对象 setArray(newElements); return true; } else //当前不是要删除的元素时,将该元素添加到新数组中 newElements[i] = elements[i]; } //比较最后一个元素是否是需要删除的元素 // special handling for last cell if (eq(o, elements[newlen])) { //将List中的数组引用指向新的数组对象 setArray(newElements); return true; } } return false; } finally { lock.unlock(); } } //从指定的位置在数组中顺序查找元素 private static int indexOf(Object o, Object[] elements, int index, int fence) { if (o == null) { //当查找的元素是null时 for (int i = index; i < fence; i++) if (elements[i] == null) return i; } else { for (int i = index; i < fence; i++) if (o.equals(elements[i])) return i; } return -1; } //查找元素是否在List中 public boolean contains(Object o) { Object[] elements = getArray(); return indexOf(o, elements, 0, elements.length) >= 0; } //从顺序查找指定元素 public int indexOf(Object o) { Object[] elements = getArray(); return indexOf(o, elements, 0, elements.length); } //从指定位置顺序查找指定元素 public int indexOf(E e, int index) { Object[] elements = getArray(); return indexOf(e, elements, index, elements.length); } //从指定的位置在数组中倒序查找元素 private static int lastIndexOf(Object o, Object[] elements, int index) { if (o == null) { //当查找的元素是null时 for (int i = index; i >= 0; i--) if (elements[i] == null) return i; } else { for (int i = index; i >= 0; i--) if (o.equals(elements[i])) return i; } return -1; } //从倒序查找指定元素 public int lastIndexOf(Object o) { Object[] elements = getArray(); return lastIndexOf(o, elements, elements.length - 1); } //从指定位置倒序查找指定元素 public int lastIndexOf(E e, int index) { Object[] elements = getArray(); return lastIndexOf(e, elements, index); }