Concurrent List source code analysis

The only concurrent List in the concurrent package is CopyOnWriteArrayList.

CopyOnWriteArrayList is a thread-safe ArrayList, and modification operations are performed on a copied array (snapshot) of the underlying layer, that is, the copy-on-write strategy is used.

Insert image description here

In the class diagram of CopyOnWriteArrayList, each CopyOnWriteArrayList object has an array array object to store specific elements, and the ReentrantLock exclusive lock object is used to ensure that only one thread modifies the array at the same time.

Just remember here that ReentrantLock is an exclusive lock and only one thread can acquire it at the same time. The locks in JUC will be introduced later.

If we were asked to make a copy-on-write thread-safe list, what would we do? What points should we consider?

  • When is the list initialized? What is the number of initialized list elements? Is the list limited in size?
  • How to ensure thread safety, for example, how to ensure thread safety when multiple threads are reading and writing?
  • How to ensure data consistency when using iterators to traverse a list?

Main method source code analysis

initialization

First, let’s look at the parameterless constructor. The following code internally creates an Object array of size 0 as the initial value of the array.
Insert image description here
Then look at the parameterized constructor.

Insert image description here

Add element

The functions used to add elements in CopyOnWriteArrayList include add(E e), add(int index,E element), addIfAbsent(E e) and addAllAbsent(Collection<?extends E> c). Their principles are similar, so add (E e) Let’s take an example to explain.

Insert image description here

In the above code, the thread that calls the add method will first execute code (1) to acquire the exclusive lock. If multiple threads call the add method, only one thread will acquire the lock, and other threads will be blocked and suspended until the lock is freed.

Therefore, after a thread acquires the lock, it is guaranteed that other threads will not modify the array while the thread is adding elements.

After the thread acquires the lock, it executes code (2) to obtain the array, and then executes code (3) to copy the array to a new array (from here you can know that the size of the new array is the original array size increased by 1, so CopyOnWriteArrayList is an unbounded list), and the new array is Add additional elements to the new array.

Then execute code (4) to replace the original array with the new array and release the lock before returning.

Due to the lock, the entire add process is an atomic operation.

It should be noted that when adding elements, a snapshot is first copied, and then added to the snapshot, rather than directly to the original array.

Get the element at the specified position

Use E get(int index) to get the element whose subscript is index. If the element does not exist, an IndexOutOfBoundsException exception will be thrown.
Insert image description here
In the above code, when thread ), this is a two-step operation, but no locking synchronization is performed during the entire process.

Assume that the content of the List is as shown in the figure at this time, with three elements 1, 2, and 3 in it.

Insert image description here

Since there is no lock when executing steps A and B, this may cause another thread y to perform a remove operation after thread x finishes executing step A and before executing step B. Suppose element 1 is to be deleted.

The remove operation will first acquire an exclusive lock, and then perform a copy-on-write operation, that is, copy a copy of the current array, and then delete element 1 that thread x wants to access through the get method in the copied array, and then let the array point to the copied array.

At this time, the reference count of the array pointed to by array is 1 instead of 0, because thread x is still using it. At this time, thread x starts to execute step B. The array operated by step B is the array before thread y deletes the element.

Insert image description here
Therefore, although thread y has deleted the element at index, step B of thread x will still return the element at index. This is actually a weak consistency problem caused by the copy-on-write strategy.

Modify the specified element

Use E set(int index, E element) to modify the value of the specified element in the list. If the element at the specified position does not exist, an IndexOutOfBoundsException exception will be thrown.

Insert image description here
The above code first acquires an exclusive lock to prevent other threads from modifying the array, then obtains the current array, and calls the get method to obtain the element at the specified position. If the element value at the specified position is inconsistent with the new value, a new array is created and the elements are copied. , then modify the element value at the specified position on the new array and set the new array to array.

If the value of the element at the specified position is the same as the new value, in order to ensure volatile semantics, the array still needs to be reset, although the contents of the array have not changed.

Delete element

To delete the specified elements in the list, you can use methods such as E remove(int index), boolean remove(Object o), and boolean remove(Object o, Object[] snapshot, int index). Their principles are the same. The remove(int index) method is explained below.

Insert image description here
The above code is actually similar to the code for adding new elements. It first obtains an exclusive lock to ensure that other threads cannot modify the array during the deletion of data, then obtains the elements to be deleted in the array, and copies the remaining elements to the new array, and then uses The new array replaces the original array, and finally the lock is released before returning.

Weakly consistent iterators

Iterators can be used to traverse list elements. Before explaining what weak consistency of iterators is, let's give an example to illustrate how to use iterators.

Insert image description here
The hasNext method of the iterator is used to determine whether there are still elements in the list, and the next method specifically returns the element. Okay, let’s take a look at the weak consistency of the iterator in CopyOnWriteArrayList. The so-called weak consistency means that after the iterator is returned, additions, deletions and changes to the list by other threads are not visible to the iterator. Let’s take a look at this. How to do it.

Insert image description here
When calling the iterator() method to obtain an iterator, a COWIterator object will actually be returned. The snapshot variable of the COWIterator object saves the contents of the current list, and the cursor is the subscript of the data when traversing the list.

Why is it said that snapshot is a snapshot of list? It is obviously a reference passed by a pointer, not a copy.

If other threads do not add, delete or modify the list while the thread uses the returned iterator to traverse the elements, then the snapshot itself is an array of the list because they are reference relationships.

But if other threads add, delete, or modify the list during the traversal, then the snapshot is a snapshot, because the array in the list is replaced by the new array after the addition, deletion, or modification, and the old array is referenced by the snapshot at this time.

This also shows that after obtaining the iterator, when using the iterator element, the additions, deletions, and modifications to the list made by other threads are not visible because they operate two different arrays, which is weak consistency.

The following is an example to demonstrate the effect of weak consistency of iterators under multi-threading.
Insert image description here
Insert image description here

In the above code, the main function first initializes arrayList, and then obtains the arrayList iterator before starting the thread.

After the sub-thread threadOne is started, it first modifies the value of the first element of arrayList, and then deletes the elements with subscripts 2 and 3 in arrayList.

After the sub-thread completes execution, the main thread uses the obtained iterator to traverse the array elements. From the output results, we know that none of the operations performed in the sub-thread took effect. This is a manifestation of the weak consistency of the iterator.

It should be noted that the operation of obtaining the iterator must be performed before the sub-thread operation.

Guess you like

Origin blog.csdn.net/zhuyufan1986/article/details/135456564