玩转并发:读多写少?你可以试试CopyOnWriteArrayList

在这里插入图片描述

介绍

我原来遇到这样一种场景,我们将一些配置信息存在数据库中,但这种配置信息访问的频率非常高,如果每次从数据库中查询,会明显降低效率。后来我就在每次启动项目的时候把数据库中的数据加载到本地缓存中,当配置发生变化时同步更新缓存

本地缓存结构如下

Map<String, List<Integer>> cache = new ConcurrentHashMap<>();

这个缓存有什么问题呢?

如果这个List的实现类是ArrayList,那么可能会发生线程安全问题

如果List的实现类是Collections.synchronizedList或者Vector,还会有问题吗?

其实还是有问题的,因为不管ArrayList,Collections.synchronizedList还是Vector,在迭代期间是不允许编辑的,如果在迭代期间进行添加或者删除等操作,则会抛出ConcurrentModificationException,适用起来还是不方便

还有哪些List接口的实现类能同时解决上面的两种问题呢?

答案就是CopyOnWriteArrayList和CopyOnWriteArraySet,其实CopyOnWriteArrayList和CopyOnWriteArraySet背后的原理就是Copy-on-Write(写时复制),即在容器中的元素被修改时,复制数组,在复制的数组上做修改。当修改完毕用复制的数组替代旧的数组

从源码看一下具体实现

读取数组内容

读取数组内容不需要加锁,直接从数组中取值

// CopyOnWriteArrayList
public E get(int index) {
    
    
    return get(getArray(), index);
}

final Object[] getArray() {
    
    
    return array;
}

private E get(Object[] a, int index) {
    
    
    return (E) a[index];
}

注意array用volatile修饰(保证可见性),当写线程把array地址更新后,其他线程能看到更新后的array

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();
    }
}

在迭代期间,如果需要修改元素,此时修改的是复制的数组,而迭代的原来的数据,所以不会发生ConcurrentModificationException,但是读取的时候可能会发生数据不一致的情况,因为读和写分别操作的是2个数组

@Test
public void cowTest() {
    
    
    List<Integer> list = new CopyOnWriteArrayList<>(new Integer[]{
    
    1, 2, 3});
    System.out.println(list);
    Iterator it1 = list.iterator();
    list.add(4);
    System.out.println(list);
    Iterator it2 = list.iterator();
    System.out.println("---it1---");
    it1.forEachRemaining(System.out::println);
    System.out.println("---it2---");
    it2.forEachRemaining(System.out::println);
}

适用场景

  1. 业务场景读多写少
  2. 对数据一致性要求不高可以考虑使用COW容器

参考博客

[1]https://www.cnblogs.com/dolphin0520/p/3933551.html

猜你喜欢

转载自blog.csdn.net/zzti_erlie/article/details/113945039
今日推荐