- 浏览: 842518 次
- 性别:
- 来自: 深圳
文章分类
最新评论
-
renzhengzhi:
请教一下楼主,公有云和私有云的开发,具体要做哪些工作呢?我拿到 ...
4,云的4 种模式 -
SangBillLee:
我用了solr5.5,用SolrQuery查询,没法高亮,不知 ...
solr5.x快速入门 -
lw900925:
这翻译读起来真是别扭。
solr in action翻译-第一章1.1 -
springjpa_springmvc:
spring mvc demo教程源代码下载,地址:http: ...
Spring MVC性能提升 -
h416373073:
正遇到了此问题不知如何解决, 多谢分享
solr错误
除了加锁外,其实还有一种方式可以防止并发修改异常,这就是将读写分离技术(不是数据库上的)。
先回顾一下一个常识:
1、JAVA中“=”操作只是将引用和某个对象关联,假如同时有一个线程将引用指向另外一个对象,一个线程获取这个引用指向的对象,那么他们之间不会发生ConcurrentModificationException,他们是在虚拟机层面阻塞的,而且速度非常快,几乎不需要CPU时间。
2、JAVA中两个不同的引用指向同一个对象,当第一个引用指向另外一个对象时,第二个引用还将保持原来的对象。
基于上面这个常识,我们再来探讨下面这个问题:
在CopyOnWriteArrayList里处理写操作(包括add、remove、set等)是先将原始的数据通过JDK1.6的Arrays.copyof()来生成一份新的数组
然后在新的数据对象上进行写,写完后再将原来的引用指向到当前这个数据对象(这里应用了常识1),这样保证了每次写都是在新的对象上(因为要保证写的一致性,这里要对各种写操作要加一把锁,JDK1.6在这里用了重入锁),
然后读的时候就是在引用的当前对象上进行读(包括get,iterator等),不存在加锁和阻塞,针对iterator使用了一个叫 COWIterator的阉割版迭代器,因为不支持写操作,当获取CopyOnWriteArrayList的迭代器时,是将迭代器里的数据引用指向当前引用指向的数据对象,无论未来发生什么写操作,都不会再更改迭代器里的数据对象引用,所以迭代器也很安全(这里应用了常识2)。
CopyOnWriteArrayList中写操作需要大面积复制数组,所以性能肯定很差,但是读操作因为操作的对象和写操作不是同一个对象,读之间也不需要加锁,读和写之间的同步处理只是在写完后通过一个简单的“=”将引用指向新的数组对象上来,这个几乎不需要时间,这样读操作就很快很安全,适合在多线程里使用,绝对不会发生ConcurrentModificationException ,所以最后得出结论:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里,比如缓存。
在你的应用中有一个列表(List),它被频繁的遍历,但是很少被修改。像“你的主页上的前十个分类,它被频繁的访问,但是每个小时通过Quartz的Job来调度更新”。
如果你使用ArrayList来作为该列表的数据结构并且不使用同步(synchronization),你可能会遇到ConcurrentModificationException,因为在你使用Quartz的Job修改该列表时,其他的代码可能正在遍历该列表。
有些开发人员可能使用Vector或Collections.synchronizedList(List<T>)的方式来解决该问题。但是这并没有效果!虽然在列表上add(),remove()和get()方法现在对线程是安全的,但遍历时仍然会抛出ConcurrentModificationException!在你遍历在列表时,你需要在该列表上使用同步,同时,在使用Quartz修改它时,也需要使用同步机制。这对性能和可扩展性来说是一个噩梦。同步需要在所有的地方出现,仅仅是因为每个小时都需要做更新。
幸运的是,这里有更好的解决方案。使用CopyOnWriteArrayList。
当列表上的一个结构修改发生时,一个新的拷贝(copy)就会被创建。这在经常发生修改的地方使用,将会很低效。遍历该列表将不会出现ConcurrentModificationException,因为该列表在遍历时将不会被做任何的修改。
另一种避免添加同步代码但可以避免并发修改问题的方式是在调度任务中构建一个新的列表,然后将原来指向到列表上的引用赋值给新的列表。在JVM中,赋值一个新的引用是原子操作。这种方式在使用旧的遍历方式(for (int i=0; i<list.size(); i++) { … list.get(i) …})时将无效(也会出错)。切换的列表中的大小将引发新的错误产生。更加糟糕的是因为改变是在不同的线程中发生的,所以还会有很多潜在的问题。使用volatile关键字可能会有所帮助,但是对列表大小的改变依然会有问题。
内存一致性和刚发生后保证了CopyOnWriteArrayList的可用性。同时,代码变得更简单,因为根本不需要使用volatile关键字或同步。更少的代码,更少的bug!
CopyOnWriteArrayList的另一个使用案例是观察者设计模式。如果事件监听器由多个不同的线程添加和移除,那么使用CopyOnWriteArrayList将会使得正确性和简单性得以保证。
概述
CopyOnWriteArrayList是jdk concurrent包中提供的一个非阻塞型的,线程安全的List实现。
private volatile transient Object[] array; final Object[] getArray() { return array; } final void setArray(Object[] a) { array = a; }
- CopyOnWriteArrayList():构造一个内容为空的对象
- CopyOnWriteArrayList(Collection<? extends E> c):根据传入参数中的内容,构造一个新的对象
- CopyOnWriteArrayList(E[] toCopyIn):根据传入数组中的内容,构造一个新的对象
public CopyOnWriteArrayList() { setArray(new Object[0]); } /** * 根据传入参数c的长度,构造一个同样长度的Object[]对象,并且将c的内容,依次填入此Object[]对象中
* 注意:
* 1. 这里对于c中内容的复制,是浅拷贝而非深拷贝
* 2. 这里的构造函数,未显式判断c是否为null,实际上如果c为null,会抛出空指针异常 */ public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements = c.toArray(); if (elements.getClass() != Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); setArray(elements); } /** * 根据传入参数的长度,构造出一个同样长度,内容一致的数组对象,封装在CopyOnWriteArrayList中
* 注意:
* 1. 这里对于数组内容的复制,是浅拷贝而非深拷贝
* 2. 这里的构造函数,未显式判断传入参数是否为null,实际上如果传入参数为null,会抛出空指针异常 */ public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }
public E get(int index) { return (E)(getArray()[index]); }
public int indexOf(Object o) { /*
* 在取数组元素前,获取当前时刻array对象的引用至关重要
* 在后续文章描述中可以知道,CopyOnArrayList在set操作时,会更新array对象的引用
* 这里如果不事先获得引用,那么后面实际的indexOf操作,会因为并发问题,得到意想不到的结果,还可能出现数组越界异常
*/ Object[] elements = getArray(); return indexOf(o, elements, 0, elements.length); }
private static int indexOf(Object o, Object[] elements, int index, int fence) { if (o == 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; }
public Object[] toArray() { Object[] elements = getArray(); return Arrays.copyOf(elements, elements.length); }
public <T> T[] toArray(T a[]) { Object[] elements = getArray(); int len = elements.length; if (a.length < len) return (T[]) Arrays.copyOf(elements, len, a.getClass()); else { System.arraycopy(elements, 0, a, 0, len); if (a.length > len) a[len] = null; return a; } }
public Object clone() { try { CopyOnWriteArrayList c = (CopyOnWriteArrayList)(super.clone()); c.resetLock(); return c; } catch (CloneNotSupportedException e) {
/* 实际不会发生,但因为clone()接口声明返回此异常,所以这里会这样写 */ throw new InternalError(); } }
写操作的并发安全性
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 { /*
* Not quite a no-op; ensures volatile write semantics
* 个人理解,这个地方没什么作用,只是为了部全set的语义
*/ 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;
/* 这个判断必须放到lock内,否则在多线程并发访问条件下,可能会出现错误,
* 例如,一个线程判断其不会抛出异常,另一个线程立即修改了数组长度,导致后续操作均会失败
*/ 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(); } }
也可以直接移除list中的某个对象,源码如下: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) { // 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]; 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; /* 找到了需要删除的元素,并正常删除,返回true */ } else { newElements[i] = elements[i];
} } /* 当需要移除的元素,在数组最后,直接将新的数组赋值过去 */ if (eq(o, elements[newlen])) { setArray(newElements); return true; } }
/* 未找到需要删除的元素,返回false */ return false; } finally { lock.unlock(); } }
发表评论
-
java实现分治法
2014-01-10 15:58 2636在计算机科学 ... -
log4j.appender.stdout.layout.ConversionPattern= [QC] %p [%t] %C.%M(%L) | %m%n
2013-12-19 10:25 7637log4j.appender.stdout.layout.C ... -
数据结构5
2013-12-14 16:20 810串的抽象数据类型定义 ADT String{ 数据对象: ... -
数据结构4
2013-12-14 16:19 829栈类型的实现 顺序栈 链栈 顺序栈 类似于线性 ... -
数据结构3
2013-12-09 10:11 666栈和队列 1,栈的类型定义 ADT S ... -
数据结构2
2013-12-09 10:09 569线性表 线性结构是一个数据元素的有 ... -
数据结构1
2013-12-09 10:07 885,1、数据结果讨论的范畴 算法+数据结构=程序 程序设计 ... -
AtomicInteger简介
2013-10-12 10:40 972这个类真的非常实用,更重要的是 它确实非常简单: 附上自己 ... -
Java获取当前路径
2013-09-04 11:02 7841、利用System.getProperty()函数获取当前 ... -
ReentrantLock
2012-12-15 20:15 1016ReentrantLock 的lock机制有2种,忽略中断锁和 ... -
ResourceBundle和Properties
2012-12-15 20:12 1055一般来说,ResourceBundle类通常是用于针对不同的语 ... -
Java并发编程之ConcurrentHashMap
2012-12-13 21:00 841ConcurrentHashMap ConcurrentHa ... -
getResource()和getResourceAsStream以及路径问题
2012-12-07 11:29 815用JAVA获取文件,听似简单,但对于很多像我这样的新人来说,还 ... -
关于List的add方法与addAll方法的区别
2012-02-16 17:41 1625add是将传入的参数作为当前List中的一个Item存储,即使 ... -
java.lang.OutOfMemoryError: Java heap space 解决方法
2012-02-16 17:00 1055java.lang.OutOfMemoryError: J ... -
java中有两种包的导入机制
2012-01-08 11:26 1327java中有两种包的导入机制,总结如下: 单类型导入(s ... -
jsp中文件下载的实现
2011-11-02 09:12 870jsp中实现文件下载的最简单的方式是在网页上做超级链接,如:& ... -
如何做软件需求分析(个人工作经验总结)
2011-07-13 14:11 6209目录 : 前言: ... -
提供精确的小数位四舍五入处理
2011-07-08 14:52 1416提供精确的小数位四舍五入处理,这个工具类很实用! /* ... -
java垃圾收集的算法分析
2011-01-17 17:02 965垃圾收集能自动释放内 ...
相关推荐
另外,CopyOnWriteArrayList由于其"读写分离"的思想,遍历和修改操作分别作用在不同的list容器,所以在使用迭代器进行遍历时候,不会抛出ConcurrentModificationException异常。 CopyOnWriteArrayList容器的缺点是...
在Java中,CopyOnWriteArrayList(写入时复制数组列表)是线程安全的集合类,它实现了List接口,并使用了"写入时复制"的策略来保证线程安全性。 CopyOnWriteArrayList的主要特点是:在进行修改操作(例如添加、修改...
java中,List在遍历的时候,如果被修改了会抛出java.util.ConcurrentModificationException错误。 看如下代码: import java.util.ArrayList; import java.util.List; public class Resource3 { public ...
Java中的`CopyOnWriteArrayList`是一个线程安全的列表实现,特别适合于高并发环境下的读多写少的场景。这个类的名字暗示了其工作原理:在修改(写入)时复制原有的数组,然后在新的数组上进行操作,最后将新数组替换...
在Java的并发编程中,CopyOnWriteArrayList 是一个重要的线程安全集合类,它通过写时复制(Copy-On-Write)机制实现了高效的读操作。本文将详细探讨 CopyOnWriteArrayList 的工作原理、优缺点、适用场景以及代码示例...
java遍历时可修改的容器CopyOnWriteArrayList
Java concurrency集合之 CopyOnWriteArrayList_动力节点Java学院整理,动力节点口口相传的Java黄埔军校
Java源码解析CopyOnWriteArrayList的讲解 Java源码解析CopyOnWriteArrayList是Java集合框架中一个非常重要的组件,它提供了一个线程安全的ArrayList变种,用于处理大量读取操作和少量写入操作的场景。在本文中,...
3. **并发容器**:书中详细讨论了`java.util.concurrent`包下的并发容器,如`ConcurrentHashMap`、`CopyOnWriteArrayList`和`BlockingQueue`等。这些容器设计为线程安全,可以提高多线程环境下的性能。 4. **并发...
因此,在多线程环境下,建议使用`Collections.synchronizedList()`或`CopyOnWriteArrayList`来创建线程安全的列表。 `vectorTest`可能是包含`Vector`使用实例的源代码文件,它可能演示了上述操作的实际应用。通过...
### Java线程使用教程知识点详解 #### 一、线程基础概述 - **定义与特点**:线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。Java是首个在语言级别明确支持线程特性的...
由于CopyOnWriteArrayList使用了final的ReentrantLock,所以确保了锁的不可变性,增强了并发安全性。 2. 获取锁,确保同一时刻只有一个线程能执行修改操作。 3. 检查是否需要扩容。如果当前数组已满,或者在某些...
### Java多线程与并发(14-26)-JUC集合-CopyOnWriteArrayList详解 #### 一、概述 `CopyOnWriteArrayList`是一种线程安全的集合类,它是`ArrayList`的一种特殊版本,主要通过复制底层数组的方式来保证线程安全性。...
Java 容器使用注意事项总结 源码分析: ArrayList 核心源码+扩容机制分析 LinkedList 核心源码分析 HashMap 核心源码+底层数据结构分析 ConcurrentHashMap 核心源码+底层数据结构分析 LinkedHashMap 核心源码分析 ...
3. 并发集合:探讨Java并发编程中的ConcurrentHashMap、CopyOnWriteArrayList等线程安全的集合类。 三、泛型 1. 泛型的概念:解释泛型的基本用法,如何限制类型参数,以及通配符的使用。 2. 泛型方法与泛型类:探讨...
10. **Java并发编程**:包括线程池、锁机制(如synchronized、ReentrantLock)、并发容器(如ConcurrentHashMap、CopyOnWriteArrayList)以及并发工具类(如CountDownLatch、CyclicBarrier)。 这些是Java基础知识...
6. **并发集合**:Java提供了线程安全的集合,如`ConcurrentHashMap`、`CopyOnWriteArrayList`等,这些集合在并发环境中性能优异。书中会分析它们的设计原理和使用场景。 7. **线程池**:`ExecutorService`是线程池...
文档可能还会深入到Java的并发容器,如ConcurrentHashMap、CopyOnWriteArrayList等,它们是线程安全的,适合多线程环境下的数据存储和操作。线程安全的类通常使用了内部锁机制(如synchronized)或者其他并发控制...