Java concurrent programming-copy-on-write container

The interviewer asks what is COW

"Damn? Why do you curse when I come for an interview?".

Interviewer: "No, it's not the one. It's the English COW."

"......cow?".

The interviewer was ashamed..."Do you know that you copy the container while writing?"

Through this small example (not real), I will deepen your impression of this abbreviation.

Copy-On-Write is referred to as COW. The popular understanding is that when we add elements to a container, we do not directly add to the current container, but first copy the current container to copy a new container, and then add elements to the new container. After adding the elements, Then point the reference of the original container to the new container. The advantage of this is that we can read the CopyOnWrite container concurrently without locking, because the current container will not add any elements. Therefore, the CopyOnWrite container is also an idea of ​​separation of reading and writing, with different containers for reading and writing. Starting from JDK1.5, the Java Concurrency Package provides two concurrent containers implemented using the CopyOnWrite mechanism. They are CopyOnWriteArrayList and CopyOnWriteArraySet.

Core code

public E set(int index, E element) { 
        final ReentrantLock lock = this.lock; 
        lock.lock();//Get the lock 
        try { 
            Object[] elements = getArray();//Get a copy of the current container array 
            E oldValue = get(elements, index);//Get the current value of the element corresponding to the index position 

            if (oldValue != element) { 
                int len ​​= elements.length; 
                //Create a new array newElements and copy the elements over 
                Object[] newElements = Arrays.copyOf(elements, len); 
                //Replace the element at index position in the new array with element 
                newElements[index] = element; 
                //This step is the key, the function is to point the reference of the array in the container to the modified one Array, namely newElements 
                setArray(newElements); 
            } else {
                //The value of the element at the index position is equal to the element, so the container array is not modified 
                setArray(elements); 
            } 
            return oldValue; 
        } finally { 
            lock.unlock();//Unlock 
        } 
    }

We can see that in the set method, we first obtain a copy of the current array to obtain a new array, and then complete the operation we want on this new array. When the operation is completed, the reference of the original array is pointed to the new array.

During this process we found that a lock was added. The reason is that the process of array copying is not an atomic operation, so write-write separation is required.

In contrast to read operations, when the write operation is completed, the reference of the original array to the new array can be regarded as an atomic operation. Therefore, there is no need to lock any read operation, and it is guaranteed that the complete snapshot data of the List at the moment of reading is read.

No matter how the List itself changes, what the iterator can perceive is the state of the List at the moment it is created. Any other thread's changes to the List are invisible to this iterator. There will be no situation where the iterator of ConcurrentHashMap may read the intermediate state of the container during the modification process of other threads. Because CopyOnWriteArrayList read operations cannot perceive the latest changing data, CopyOnWriteArrayList is also weakly consistent like ConcurrentHashMap.

to sum up

Let's compare the most common concurrent container ConcurrentHashMap to summarize:

  1. ConcurrentHashMap and CopyOnWriteArrayList are both lock-free reads, so when a read operation occurs, it cannot be ensured that the write operations of all other threads have been completed, and they cannot be used in scenarios that require strong data consistency.
  2. Both ConcurrentHashMap and CopyOnWriteArrayList can ensure that the completed write operation can be sensed when reading.
  3. ConcurrentHashMap read operations may perceive the intermediate state of other threads writing to the container at the same time. CopyOnWriteArrayList will always only read the snapshot state of the container at the time of reading.
  4. ConcurrentHashMap uses lock segmentation technology to reduce the scope of locks and increase the amount of concurrent writes. CopyOnWriteArrayList uses the copy-on-write technology to ensure that data is written concurrently without causing interference to read operations that have been opened. It is not difficult to find that the lock granularity of CopyOnWriteArrayList is quite large, so in the scene of excessive concurrency, the writing efficiency is very poor.
  5. ConcurrentHashMap is suitable for scenarios where there is no strong consistency requirement for data access under high concurrency. CopyOnWriteArrayList is suitable for scenarios with strong element coupling in concurrent containers, which can ensure that the data in each snapshot is consistent. If there are too many elements in the concurrent container, the memory cost of copying will be too high, and the copying efficiency will be further reduced.

In actual development, you can choose a concurrent container according to the business scenario.

Guess you like

Origin blog.csdn.net/weixin_47184173/article/details/115269145