java集合类-6-CopyOnWrite
2019-09-30 本文已影响0人
宠辱不惊的咸鱼
CopyOnWriteArrayList
概述
- JDK 1.5出现
- 写时复制容器
- 添加元素时,不直接往当前容器添加
- 将当前容器进行Copy,往新容器添加元素
- 添加完,将原容器引用指向新容器
- 好处
- 读写分离
机制
- 添加时需加锁,否则多线程写时会Copy出N个副本
- 读时无需加锁,若读时有线程正在向CopyOnWriteArrayList添加数据,会读到旧数据,因为写不会锁住旧的
添加
private transient volatile Object[] array;
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();
}
}
- 写
- 加锁
- 复制(可以看到效率是蛮低的)
- 加入Element
- 重定向数组引用
- 释放锁
批量添加
public boolean addAll(Collection<? extends E> c) {
Object[] cs = (c.getClass() == CopyOnWriteArrayList.class) ? ((CopyOnWriteArrayList<?>)c).getArray() : c.toArray();
if (cs.length == 0)
return false;
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len == 0 && cs.getClass() == Object[].class)
setArray(cs);
else {
Object[] newElements = Arrays.copyOf(elements, len + cs.length);
System.arraycopy(cs, 0, newElements, len, cs.length);
setArray(newElements);
}
return true;
} finally {
lock.unlock();
}
}
场景
- 读多写少
- 比如白名单,黑名单,商品类目的访问和更新场景。假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索;这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次;当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索
缺点
- 内存占用问题
- 写操作的时候,内存里会同时驻扎两个对象的内存,旧的对象和新写入的对象(注意:在复制的时候只是复制容器里的引用,只是在写的时候会创建新对象添加到新容器里,而旧容器的对象还在使用,所以有两份对象内存)
- 如果这些对象占用的内存比较大,比如说200M左右,那么再写入100M数据进去,内存就会占用300M,那么这个时候很有可能造成频繁的Yong GC和Full GC
- 之前我们系统中使用了一个服务由于每晚使用CopyOnWrite机制更新大对象,造成了每晚15秒的Full GC,应用响应时间也随之变长
- 针对内存占用问题,可以通过压缩容器中的元素的方法来减少大对象的内存消耗,比如,如果元素全是10进制的数字,可以考虑把它压缩成36进制或64进制;或者不使用CopyOnWrite容器,而使用其他的并发容器,如ConcurrentHashMap
- 数据一致性问题
- 只能保证数据的最终一致性,不能实时一致性。所以如果你希望写入的的数据,马上能读到,请不要使用CopyOnWrite容器
性能测试
- CopyOnWriteArrayList在线程对其进行变更操作的时候,会拷贝一个新的数组以存放新的字段,因此写操作性能很差
- Collections.synchronizedList读操作采用了synchronized,因此读性能较差
public class App {
private static List<String> arrayList = Collections.synchronizedList(new ArrayList<String>());
private static List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<String>();
private static CountDownLatch cdl1 = new CountDownLatch(2);
private static CountDownLatch cdl2 = new CountDownLatch(2);
private static CountDownLatch cdl3 = new CountDownLatch(2);
private static CountDownLatch cdl4 = new CountDownLatch(2);
static class Thread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
arrayList.add(String.valueOf(i));
cdl1.countDown();
}
}
static class Thread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10000; i++)
copyOnWriteArrayList.add(String.valueOf(i));
cdl2.countDown();
}
}
static class Thread3 extends Thread1 {
@Override
public void run() {
int size = arrayList.size();
for (int i = 0; i < size; i++)
arrayList.get(i);
cdl3.countDown();
}
}
static class Thread4 extends Thread1 {
@Override
public void run() {
int size = copyOnWriteArrayList.size();
for (int i = 0; i < size; i++)
copyOnWriteArrayList.get(i);
cdl4.countDown();
}
}
public static void main(String[] args) throws InterruptedException {
long start1 = System.currentTimeMillis();
new Thread1().start();
new Thread1().start();
cdl1.await();
System.out.println("arrayList add: " + (System.currentTimeMillis() - start1));
long start2 = System.currentTimeMillis();
new Thread2().start();
new Thread2().start();
cdl2.await();
System.out.println("copyOnWriteArrayList add: " + (System.currentTimeMillis() - start2));
long start3 = System.currentTimeMillis();
new Thread3().start();
new Thread3().start();
cdl3.await();
System.out.println("arrayList get: " + (System.currentTimeMillis() - start3));
long start4 = System.currentTimeMillis();
new Thread4().start();
new Thread4().start();
cdl4.await();
System.out.println("copyOnWriteArrayList get: " + (System.currentTimeMillis() - start4));
}
}
map-002.jpg