Jun 01, 2021 Article blog
What
CopyOnWrite
is, literally, is copying on write.
It looks very simple, so how exactly is replication done when writing?
First of all, talk about thought, how to achieve and so on analysis
CopyOnWrite
idea is that when you add an element to a container, instead of adding it directly to the current container, you copy it out of a new container, add elements inside the new container, add the reference to the original container, and then point the reference to the original container to the new container, which enables write-time replication
Do you remember when you talk about databases, you usually say that the master is separated from replication, reading and writing?
CopyOnWrite
design ideas are not as detached from what is often said as master-to-copy, read-write separation?
(Recommended tutorial: Java tutorial)
Once you understand the concept, you should have a better understanding of its advantages and disadvantages
The advantage is that read and write can be performed in parallel, because the original container is read, the new container is written, they do not affect each other, so reading and writing can be performed in parallel, in some high concurrent scenarios, you can improve the response time of the program
But, as you can see,
CopyOnWrite
copied a new container out at the time of writing, so consider its memory overhead and go back to the idea that has been emphasized in learning algorithms: swap space for time
It is important to note that it only guarantees the final consistency of the data. Because at the time of reading, the contents of the read are the contents of the original container, and the newly added content is unreadable
Based on its advantages and disadvantages, one conclusion should be drawn:
CopyOnWrite
is suitable for scenarios where there are very few writes, and can tolerate temporary inconsistencies in read and write If your scenario isn't suitable, consider using something else
It is also important to note that when writing, it copies a new container, so if there is a write requirement, it is best to write in bulk, because each time you write, the container is copied, and if you can reduce the number of writes, you can reduce the number of copies of the container
Under the
JUC
package,
CopyOnWrite
ideas are implemented by
CopyOnWriteArrayList
and
CopyOnWriteArraySet
and this article focuses on making it clear that
CopyOnWriteArrayList
In
CopyOnWriteArrayList
it is important to note the
add
method:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
// 在写入的时候,需要加锁,如果不加锁的话,在多线程场景下可能会被 copy 出 n 个副本出来
// 加锁之后,就能保证在进行写时,只有一个线程在操作
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();
}
}
Locks are required when writing, but not when read
Because the element of the original array is read, there is no effect on the new array, and locking increases performance overhead
public E get(int index) {
return get(getArray(), index);
}
CopyOnWrite
is under the
JUC
package, so it keeps the thread safe
Let's do a little
demo
verification:
@Slf4j
public class ArrayListExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static List<Integer> list = new ArrayList<>();
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(()->{
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("exception",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}",list.size());
}
private static void update(int i){
list.add(i);
}
}
It's 5000 client requests, 200 threads are requesting at the same time, I'm using
ArrayList
implementation, let's look at the print results:
If it's thread-safe, the end result should be 5000, and run a few more times and you'll find that each program executes differently
What if it's
CopyOnWriteArrayList
@Slf4j
public class CopyOnWriteArrayListExample {
// 请求总数
public static int clientTotal = 5000;
// 同时并发执行的线程数
public static int threadTotal = 200;
private static List<Integer> list = new CopyOnWriteArrayList<>();
public static void main(String[] args) throws Exception{
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal; i++) {
final int count = i;
executorService.execute(()->{
try {
semaphore.acquire();
update(count);
semaphore.release();
} catch (Exception e) {
log.error("excepiton",e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("size:{}",list.size());
}
private static void update(int i){
list.add(i);
}
}
Run a few more times and the results are the same:
Thus,
CopyOnWriteArrayList
is thread-safe.
(Recommended micro-class: Java micro-class)
Source: Java Geek Technology
Author: Duck Blood Fans
The above is about Java concurrent CopyOnWrite related to the introduction, I hope to help you.