I kept running just to catch up with myself who had high hopes. -Livingston
0 Preface
We know that ArrayList non-thread-safe, you need to own or use a lock Collections.synchronizedList
packaging container provides concurrency thread-safe mechanisms to achieve from using CopyOnWrite JDK1.5 start JUC in the List -. CopyOnWriteArrayList, referred COW
1 CopyOnWrite design ideas
1.1 Basic concepts
CopyOnWrite copy-on-write. Generally speaking, when we add elements to a container, we don't directly add to the current container, but first copy the current container out of a new container, add elements to the new container, add elements Then, point the original container to the new container. That is, everyone is sharing the same content at the beginning. When someone wants to modify the content, they will actually copy the content to form a new content and then change it. This is a kind of Delay lazy strategy.
1.2 Design advantages
The CopyOnWrite container can be read concurrently without locking, because the current container will not add any elements. So this is also a separate read and write idea, read and write different containers.
2 Inheritance system
- Similar to the inheritance system of ArrayList
3 Properties
- Locks to protect all modifiers
- Arrays that can only be accessed via getArray / setArray
- lock memory offset
4 Construction method
4.1 No parameters
- Create an empty list
4.2 Participation
- Create a list that contains the elements of the specified collection, whose order is returned by the collection's iterator.
- Create a list holding a copy of the given array
Let's start looking at the source code and how to copy it on write.
5 add (E and)
Adding elements to the COW requires locking, otherwise N copies will be copied during concurrent writing!
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 1.加锁
lock.lock();
try {
// 得到原数组
Object[] elements = getArray();
int len = elements.length;
// 2.复制出新数组,加一是因为要添加yi'ge'yuan's
Object[] newElements = Arrays.copyOf(elements, len + 1);
// 把新元素添加到新数组里,直接放在数组尾部
newElements[len] = e;
// 把原数组引用指向新数组
setArray(newElements);
return true;
} finally {
// finally 里面释放锁,保证即使 try 发生了异常,仍然能够释放锁
lock.unlock();
}
}
复制代码
getArray
- Get an array. Non-priavte, so that it can also be accessed from the CopyOnWriteArraySet class (which directly combines CopyOnWriteArrayList as a member variable)
setArray
- Set reference to new array
Both are locked, why do you need to copy the array without directly modifying the original array?
- volatile is modified by an array reference! Simply modify the value of a few elements in the original array. This operation cannot be used for visibility. You must modify the memory address of the array
- Executing copyOf on the new array has no effect on the original array. Only after the new array is completely copied, can it be accessed externally, avoiding the possible adverse effects of data changes in the original array
6 get
get(int index)
- Read the specified position element
get(Object[] a, int index)
There is no need to add a lock when reading. If other threads are adding data to the ArrayList while reading, the read will still only read the old data, because the old array will not be locked when writing.
7 remove
7.1 Delete specified index
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 {
// 若删除的数据在数组中间:
// 1. 设置新数组的长度减一,因为是减少一个元素
// 2. 从 0 拷贝到数组新位置
// 3. 从新位置拷贝到数组尾部
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();
}
}
复制代码
Still three axes:
- Lock
- Depending on where the index is deleted, make different policy copies
- Unlock
7.2 Bulk delete
public boolean removeAll(Collection<?> c) {
if (c == null) throw new NullPointerException();
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// newlen 表新数组的索引位置,新数组中存在不包含在 c 中的元素
int newlen = 0;
Object[] temp = new Object[len];
// 循环,把不包含在 c 里面的元素,放到新数组中
for (int i = 0; i < len; ++i) {
Object element = elements[i];
// 不包含在 c 中的元素,从 0 开始放到新数组中
if (!c.contains(element))
temp[newlen++] = element;
}
// 拷贝新数组,变相的删除了不包含在 c 中的元素
if (newlen != len) {
setArray(Arrays.copyOf(temp, newlen));
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
复制代码
Instead of directly deleting the array elements one by one, first judge the array values in a loop, put the data that does not need to be deleted into the temporary array, and finally the data in the temporary array is the data that we don't need to delete.
8 Summary
The CopyOnWrite concurrency container is suitable for concurrent scenarios with more reads and less writes. The CopyOnWrite container has many advantages, but there are also problems. You need to pay attention to when developing:
Memory usage issues
When writing, the memory of two objects will reside in the memory at the same time, the old object and the newly written object (only the reference in the container is copied when copying, but the new object is created and added to the new container when writing, and the old The objects of the container are still in use, so there are two copies of the object memory). If these objects occupy a large amount of memory, it is likely to cause frequent GC, and the application response time becomes longer. Memory, or not directly use the CopyOnWrite container, but use other concurrent containers, such as ConcurrentHashMap.
Data consistency issues
The CopyOnWrite container can only guarantee data, 最终一致性
but not data 实时一致性
, please use it as appropriate.