This is the 12th day of my participation in the August Text Challenge.More challenges in August
CopyOnWriteArrayList
Classes that belong to JUC
Class declaration
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Copy the code
Class variables
final transient ReentrantLock lock = new ReentrantLock();
private transient volatile Object[] array;
Copy the code
Initialize the code block
private void resetLock() {
UNSAFE.putObjectVolatile(this, lockOffset, new ReentrantLock());
}
private static final sun.misc.Unsafe UNSAFE;
private static final long lockOffset;
static {
try {
UNSAFE = sun.misc.Unsafe.getUnsafe();
Class<?> k = CopyOnWriteArrayList.class;
lockOffset = UNSAFE.objectFieldOffset
(k.getDeclaredField("lock"));
} catch (Exception e) {
throw new Error(e);
}
}
Copy the code
methods
A constructor
public CopyOnWriteArrayList() { setArray(new Object[0]); } public CopyOnWriteArrayList(Collection<? extends E> c) { Object[] elements; if (c.getClass() == CopyOnWriteArrayList.class) elements = ((CopyOnWriteArrayList<? >)c).getArray(); else { elements = c.toArray(); // c.toArray might (incorrectly) not return Object[] (see 6260652) if (elements.getClass() ! = Object[].class) elements = Arrays.copyOf(elements, elements.length, Object[].class); } setArray(elements); } final void setArray(Object[] a) { array = a; } public CopyOnWriteArrayList(E[] toCopyIn) { setArray(Arrays.copyOf(toCopyIn, toCopyIn.length, Object[].class)); }Copy the code
See:
elements = ((CopyOnWriteArrayList<? >)c).getArray(); . setArray(elements); }Copy the code
Obviously there’s a problem:
- If it’s from the sameCopyOnWriteArrayListThen the arrays in both containers refer to the same array: you can write a demo to verify this.
How do YOU ensure that containers created from other CopyonWritearRayLists are available in isolation? 【 answer: Snapshot 】
Basic method
The basic method here refers to:
- get
- set
- add
- remove
And other basic operations that array managed containers should implement.
The get method
private E get(Object[] a, int index) {
return (E) a[index];
}
public E get(int index) {
return get(getArray(), index);
}
Copy the code
The GET method itself is atomic and does not affect other operations, so no lock and copy operations are done.
Set method
public E set(int index, E element) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
E oldValue = get(elements, index);
if (oldValue != element) {
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len);
newElements[index] = element;
setArray(newElements);
} else {
// Not quite a no-op; ensures volatile write semantics
setArray(elements);
}
return oldValue;
} finally {
lock.unlock();
}
}
Copy the code
From this code, the set method does the following
-
For the container object, the set method is locked.
-
The set method compares the old and new values
-
If the old and new values are not equal (**= instead of equals**, which in the case of objects refers to addresses), then:
- Copy a new array
- The value at the target index of the new array is changed to the target value
- Sets the managed array to the new array
-
If not, plug the Elements array back in. The reason:
-
Not quite a no-op; Volatile write semantics is used to ensure volatile write semantics
- In this blog post, he explains why: To ensure the happens-before principle.
- This place stuffs the variable back in, ensuring that the external context (where the method was called) will not be reordered by the CPU.
-
-
The add method
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(); }} public void add(int index, E element) {final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); 8 int len = elements. Length; if (index > len || index < 0) throw new IndexOutOfBoundsException("Index: "+index+ ", Size: "+len); Object[] newElements; int numMoved = len - index; // If (numMoved == 0) newElements = array.copyof (Elements, len + 1); NewElements = new Object[len + 1]; newElements = new Object[len + 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index, newElements, index + 1, numMoved); } newElements[index] = element; setArray(newElements); } finally { lock.unlock(); }}Copy the code
The add method also does two things to ensure the accuracy of the operation in multi-threading:
- Use ReentrantLock to lock
- Copy the expanded array (length +1) and set the managed array to the new array after setting the last bit of the expanded array as the target object.
The remove method
Public E remove(int index) {final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; E oldValue = get(elements, index); int numMoved = len - index - 1; if (numMoved == 0) setArray(Arrays.copyOf(elements, len - 1)); else { Object[] newElements = new Object[len - 1]; System.arraycopy(elements, 0, newElements, 0, index); System.arraycopy(elements, index + 1, newElements, index, numMoved); setArray(newElements); } return oldValue; } finally { lock.unlock(); Public Boolean remove(Object o) {Object[] snapshot = getArray(); int index = indexOf(o, snapshot, 0, snapshot.length); return (index < 0) ? false : remove(o, snapshot, index); } private Boolean remove(Object o, Object[] snapshot, int index) {final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot ! = current) findIndex: { int prefix = Math.min(index, len); for (int i = 0; i < prefix; i++) { if (current[i] ! = snapshot[i] && eq(o, current[i])) {//--------1 index = i; break findIndex; } } if (index >= len) return false; if (current[index] == o)//------------------2 break findIndex; index = indexOf(o, current, index, len); //---------------3 if (index < 0) return false; } Object[] newElements = new Object[len - 1]; System.arraycopy(current, 0, newElements, 0, index); System.arraycopy(current, index + 1, newElements, index, len - index - 1); setArray(newElements); return true; } finally { lock.unlock(); }} //indexOf is omitted, using a for loop to compare with == (null) /equals (non-null) and return the index positionCopy the code
The remove method does the same as the above two methods:
- locked
- Copy the array
In the method of finding -remove, there is a small detail:
-
If it finds it, the remove method is used to lock it and contains a snapshot of the array at the time of the search.
-
If the snapshot is different from the current managed array, it is compared:
- The next step takes place only if the current array contains the element to remove. (1,2,3)
The label code block technique is used to quickly jump out of the judgment.
Enhanced List method
There are also some list-based enhancements, such as:
RemoveRange (int fromIndex, int toIndex) removeRange(int fromIndex, int toIndex) removeRange(int fromIndex, int toIndex) Public Boolean addIfAbsent(E E) {Object[] snapshot = getArray(); return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false : addIfAbsent(e, snapshot); } private boolean addIfAbsent(E e, Object[] snapshot) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] current = getArray(); int len = current.length; if (snapshot ! = current) { // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len); for (int i = 0; i < common; i++) if (current[i] ! = snapshot[i] && eq(e, current[i])) return false; if (indexOf(e, current, common, len) >= 0) return false; } Object[] newElements = Arrays.copyOf(current, len + 1); newElements[len] = e; setArray(newElements); return true; } finally { lock.unlock(); }}Copy the code
Iterator
Inner class definition
static final class COWIterator<E> implements ListIterator<E> { /** Snapshot of the array */ private final Object[] snapshot; /** Index of element to be returned by subsequent call to next. */ private int cursor; private COWIterator(Object[] elements, int initialCursor) { cursor = initialCursor; snapshot = elements; }... }Copy the code
-
Also array based, provides forward/backward traversal.
-
Do not support delete/set/add operations:
public void remove() { throw new UnsupportedOperationException(); } public void set(E e) { throw new UnsupportedOperationException(); } public void add(E e) { throw new UnsupportedOperationException(); } Copy the code
SubList
Access method
public List<E> subList(int fromIndex, int toIndex) { final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; if (fromIndex < 0 || toIndex > len || fromIndex > toIndex) throw new IndexOutOfBoundsException(); return new COWSubList<E>(this, fromIndex, toIndex); } finally { lock.unlock(); }}Copy the code
The class definition
private static class COWSubList<E> extends AbstractList<E> implements RandomAccess { private final CopyOnWriteArrayList<E> l; private final int offset; private int size; private Object[] expectedArray; // only call this holding l's lock COWSubList(CopyOnWriteArrayList<E> list, int fromIndex, int toIndex) { l = list; expectedArray = l.getArray(); offset = fromIndex; size = toIndex - fromIndex; } private void checkForComodification() {if (l.array ()! = expectedArray) throw new ConcurrentModificationException(); }... }Copy the code
SubList operations are described as follows:
-
Regardless of the category in CRUD, the flow of operations is:
- Lock the parent object and lock it
- Check boundaries to check for asynchronous edits
- Method to call the parent object
- unlock
Thread safety
COWArrayList is not completely immune to thread-safety issues.
The reason:
-
In the get method, we don’t check the index. We just get the index of the current Array, based on the current Array.
-
Remove method: copy array after assignment.
-
So there’s a possibility:
- Thread 1 enters the get method, index = size-1, which has a value.
- Thread 2 enters the remove method, thread 1 suspends, and changes the array size to size-1.
- Thread 2 terminates, thread 1 gets the time slice and continues to run. At this time, the index value of thread 1 is invalid and an out-of-bounds error occurs.
- Of course, the remove method is so much longer than the GET method, and the GET method is not locked. Basically, it is difficult for the remove method to complete the whole process in the time slice acquired when the GET method is suspended. Therefore, this place is also theoretical analysis, and it is unlikely to happen under normal circumstances.
-
Although the array is volatile, volatile does not guarantee thread hang in both threads.
-
But in the source
- The comments explicitly indicate that the GET method throws an out-of-bounds exception
- And because the GET method does not check boundaries, it may have been designed with this in mind, but not as a bug fix, just as a regular list feature.