The introduction
Interviewer: Young man, you look familiar. Did you come here for an interview last year? B: Oh, no. This is my first time here. Interviewer: Ok, let’s start today’s interview. Let’s start with something simple. What do you know about Java containers? Well, common Java containers are ArrayList(thread-safe), HashMap (thread-safe), HashSet (thread-safe), and ConcurrentHashMap (thread-safe). Is there a thread-safe ArrayList column? Fat Two: Well… I think I’ve got a knowledge blind spot. Interviewer: That’s all for today’s interview. I will have another meeting later. I will contact you if there is any notice. The above story is pure fiction, if any similar, please use this article as the main.
What is a COW
In Java, when it comes to collection containers, we usually think of HashMap, ArrayList and HasHSet, which are also the most commonly used containers in daily development. These are all non-thread-safe, so if we have a particular business that needs to use thread-safe container columns,
HashMap
You can useConcurrentHashMap
Instead.ArrayList
You can useCollections.synchronizedList()
Methods (list
Use every methodsynchronized
Modify) or useVector
(I don’t use it anymore. I use every methodsynchronized
Modify) or useCopyOnWriteArrayList
Alternative.- HasHSet can be used
Collections.synchronizedSet
Or useCopyOnWriteArraySet
Instead. CopyOnWriteArraySet why not CopyOnWriteHashSet becauseCopyOnWriteArraySet
The underlying is the adoption ofCopyOnWriteArrayList
We can see thatCopyOnWriteArrayList
Occurs multiple times in thread-safe containers. First of all, what isCopyOnWrite
?Copy-On-Write
Referred to as”COW
, is an optimization strategy used in programming.
A CopyOnWrite container is a container for copying while writing. The common 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, Copy the new container, then add elements to the new container, after adding elements, the original container reference to the new container. The advantage of this is that we can do concurrent reads to the CopyOnWrite container without locking, since no elements are being added to the current container. So the CopyOnWrite container is also an idea of read-write separation, reading and writing different containers.
Why was COW introduced
To prevent abnormal ConcurrentModificationException
In Java if we adopt the cycle of incorrect posture to traverse the List, if thrown while traversing the modify Java util. ConcurrentModificationException wrong. If you’re not familiar with iterating through an ArrayList, check out this article, “Do you know how to delete an ArrayList?”
List<String> list = new ArrayList<>();
list.add("Zhang");
list.add("Java financial");
list.add("javajr.cn");
Iterator<String> iterator = list.iterator();
while(iterator.hasNext()){
String content = iterator.next();
if("Zhang".equals(content)) {
list.remove(content);
}
}
Copy the code
The chestnut occur above Java. Util. ConcurrentModificationException abnormal, if change the ArrayList to CopyOnWriteArrayList abnormal is not going to happen.
Thread-safe containers
One thread adds data to the List, and another thread loops through the List to read data.
List<String> list = new ArrayList<>();
list.add("Zhang");
list.add("Java financial");
list.add("javajr.cn");
Thread t = new Thread(new Runnable() {
int count = 0;
@Override
public void run(a) {
while (true) {
list.add(count++ + "");
}
}
});
t.start();
Thread.sleep(10000);
for (String s : list) {
System.out.println(s);
}
Copy the code
We run the above code will occur ConcurrentModificationException abnormalities, if changed the ArrayList CopyOnWriteArrayList is everything is ok.
The realization of the CopyOnWriteArrayList
CopyOnWriteArrayList is thread-safe, so let’s take a look at how CopyOnWriteArrayList is thread-safe.
public class CopyOnWriteArrayList<E>
implements List<E>, RandomAccess.Cloneable.java.io.Serializable {
private static final long serialVersionUID = 8673264195747942595L;
/** The lock protecting all mutators */
final transient ReentrantLock lock = new ReentrantLock();
/** The array, accessed only via getArray/setArray. */
private transient volatile Object[] array;
Copy the code
CopyOnWriteArrayList and ArrayList are both implemented using an array of objects, but the array of CopyOnWriteArrayList is volatile. For advice on why volatile modifiers are needed, see “Does Java’s Synchronized Prevent Instruction Reordering?” ReentrantLock has also been added.
The add method:
public boolean add(E e) {
// Get the lock first
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
// Copy a new array
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
// Assign the value of the new array to the original array
setArray(newElements);
return true;
} finally {
/ / releases the lock
lock.unlock();
}
}
Copy the code
The above source code we can find relatively simple, there are a few points need to pay a little attention to
- The time to add data is through
ReentrantLock
Lock operation to (injdk11
“Was adoptedsynchronized
To replaceReentrantLock
Make sure that only one thread copies the array when multithreading writes, otherwise there will be multiple copies of the copied data in memory, resulting in data corruption. - Arrays are passed through
volatile
According tovolatile
的happens-before
Rule: Changes made by the writer thread to an array reference are immediately visible to the reader thread. - Copy-on-write ensures that reads and writes operate in two different data containers.
Implement a COW container yourself
Java and send package provides two concurrent containers using CopyOnWrite mechanism, they are CopyOnWriteArrayList and CopyOnWriteArraySet, But there is no CopyOnWriteHashMap. We can implement a CopyOnWriteHashMap ourselves
public class CopyOnWriteHashMap<K.V> implements Map<K.V>, Cloneable {
final transient ReentrantLock lock = new ReentrantLock();
private volatile Map<K, V> map;
public CopyOnWriteHashMap(a) {
map = new HashMap<>();
}
@Override
public V put(K key, V value) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Map<K, V> newMap = new HashMap<K, V>(map);
V val = newMap.put(key, value);
map = newMap;
return val;
} finally {
lock.unlock();
}
}
@Override
public V get(Object key) {
return map.get(key);
}
@Override
public V remove(Object key) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Map<K, V> newMap = new HashMap<K, V>(map);
if(! newMap.containsKey(key)) {
return null;
}
V v = newMap.get(key);
newMap.remove(key);
map = newMap;
return v;
}finally {
lock.unlock();
}
}
Copy the code
Above, we have implemented a simple CopyOnWriteHashMap, which only implements add, remove and get methods, and other methods can be implemented by themselves. As long as the data changes, locks will be added, while reading does not need to be locked.
Application scenarios
The CopyOnWrite concurrent container is suitable for concurrent scenarios in which there are many reads and few writes, such as the blacklist and whitelist, basic data caching of countries and cities, and system configuration. These are basically just want to start the project at the time of initialization, change frequency is very low. If a Vector is used for this read-much, read-little scenario, the collections-wrapped approach doesn’t make sense, because although multiple readers are reading from the same data container, the reader thread doesn’t modify the data in the container, so it doesn’t need to lock the read.
CopyOnWrite shortcomings
CopyOnWriteArrayList is a thread-safe version of ArrayList, but it makes a copy of the data every time it changes data, so CopyOnWriteArrayList is only suitable for read more than write less or no-lock read scenarios. If we use CopyOnWriteArrayList in a real business, it must be because the scenario fits, not because it shows off.
Memory usage problem
Since CopyOnWrite’s copy-on-write mechanism has two array objects in memory for each write operation, if the array object occupies a large amount of memory, frequent writes will result in frequent Yong and Full GC.
Data consistency issues
The CopyOnWrite container only guarantees final data consistency, not real-time data consistency. The reading thread may not immediately read the newly modified data because the modification takes place on the replica. But eventually the modification is done and the container is updated so this is the final consistency.
CopyOnWriteArrayList and Collections. SynchronizedList ()
Under the simple test the CopyOnWriteArrayList and Collections. SynchronizedList found () to read and write:
- Write in high concurrency CopyOnWriteArray than synchronous Collections. SynchronizedList one hundred times slower
- In the high performance of concurrent read-access CopyOnWriteArray than synchronous Collections. SynchronizedList dozens of times faster.
- Why is CopyOnWriteArrayList so slow for high concurrency writes? Because its Arrays. CopyOf creates new Arrays every time it adds, frequent add consumes a lot of memory requisition and release performance.
- When high concurrent read CopyOnWriteArray unlocked, Collections. SynchronizedList lock so read with low efficiency.
conclusion
When you select CopyOnWriteArrayList you must read more than you write. If reading and writing about the same suggestion choice Collections. SynchronizedList.
The end of the
- As a result of their talent and learning, it is inevitable that there will be mistakes, if you found the wrong place, but also hope that the message to me pointed out, I will correct it.
- If you think the article is good, your forwarding, sharing, appreciation, like, message is the biggest encouragement to me.
- Thank you for reading. Welcome and thank you for your attention.