Play with concurrency: read more and write less? You can try CopyOnWriteArrayList

Insert picture description here

Introduction

I originally encountered such a scenario where we store some configuration information in the database, but the frequency of access to this configuration information is very high. If you query from the database every time, it will significantly reduce the efficiency. Later, I load the data in the database into the local cache every time I start the project, and update the cache synchronously when the configuration changes.

The local cache structure is as follows

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

What's wrong with this cache?

If the implementation class of this List is ArrayList, then thread safety issues may occur

If the implementation class of List is Collections.synchronizedList or Vector, will there be problems?

In fact, there is still a problem, because regardless of ArrayList, Collections.synchronizedList or Vector, editing is not allowed during the iteration. If you add or delete during the iteration, a ConcurrentModificationException will be thrown, which is still not convenient to apply.

What other implementation classes of the List interface can solve the above two problems at the same time?

The answer is CopyOnWriteArrayList and CopyOnWriteArraySet. In fact, the principle behind CopyOnWriteArrayList and CopyOnWriteArraySet is Copy-on-Write (copy-on-write), that is, when the elements in the container are modified, the array is copied, and the modification is made on the copied array. When the modification is completed, replace the old array with the copied array

Look at the specific implementation from the source code

Read the contents of the array

Reading the contents of the array does not need to be locked, and the value is directly taken from the array

// 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];
}

Note that the array is modified with volatile (to ensure visibility). When the writer thread updates the array address, other threads can see the updated array

private transient volatile Object[] array;

Modify the contents of the array

The overall idea is to lock, copy the new array, modify the new array, and replace the old array with the new array after the modification

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

During the iteration, if you need to modify the elements, the copied array is modified at this time, and the original data of the iteration will not occur, so ConcurrentModificationException will not occur, but data inconsistency may occur when reading, because the read and write operations are performed separately Are 2 arrays

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

Applicable scene

  1. Business scenarios read more and write less
  2. Low requirements for data consistency can consider using COW containers

Reference blog

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

Guess you like

Origin blog.csdn.net/zzti_erlie/article/details/113945039