当我们并发往ArrayList中添加值的时候,当并发过大的时候会出现ConcurrentModificationException(并发修改异常),
List list =new ArrayList<>();
for (int i = 0; i <100; i++) {
new Thread(()->{
list.add("我在这里");
System.out.println(list);
}).start();
}
//*****************会报出如下异常****************
Exception in thread "Thread-86" java.util.ConcurrentModificationException
老生常谈的都知道ArrayList不安全,弄段源码,这一看就不安全了
vector安全了,但有synchronized,效率低,而且操作上扩容、修改、分配内存也不大友好(小声说:synchronized其实优化这么多次了,性能已经很可以了),
,那咋办,从标题可以看出,解决方案就是JUC的写时复制(CopyOnWrite),和redis持久化差不多。在测试CopyOnWrite的时候是安全的,在1.8的时候CopyOnWrite采用的机制是ReentrantLock
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();
}
}
而我在jdk12上看到它又采用了synchronized
咋办呢,看看为啥呗;两个都是可重入独占锁,在不涉及到中断、超时等情况时,编码时使用synchronized明显比ReentrantLock方便简洁得多;(仔细瞅瞅会发现12比之前的版本少new了一个Object数组,哈哈);
看上面的代码后也许要问了,他这也使用了同步代码块,我自己使用同步代码块用ArrayList不香吗;,这里我们就要看下this.lock和定义的数组了;
#transient(短暂的) Object对象充当写时复制的锁对象,不被序列化的锁对象
#节省空间
#volatile (易变的)是轻量级的锁,它只具备可见性,但没有原子特性
#如果一个变量加上volatile,那么只要对这个域产生了写操作,
#所有的读操作都可以看到这个修改,volatile会被立即写入到主内存中,
#而读的操作就发生在主内存中,如果多个任务在同时访问某个域,
#那么这个域就应该是volatile,volatile可以保证变量的安全
final transient Object lock = new Object();
private transient volatile Object[] array;
所以synchronized保证线程安全,里面的数据安全则交由volatile
看这张图的读方法,啥涉及锁的都没加所以可以并发读;
看了上面的两个定义大概应该懂了,CopyOnWrite适合于读多写少的业务,可以并发读,每次copy的时候对内存也是有很大消耗的,得具体业务具体分析;over
好啦,今天的不开心就止于此吧,明天依旧光芒万丈发啊,宝贝!
撒花,完结!!!
一个没有梦想的梦想家