The main contents of this paper are as follows:
All of this sample code has been updated to my Github
This article is in my Java online documentation
Java Concurrency Must Know must be series:
1. Counter the interviewer | | 14 zhang diagram is no longer afraid of being asked a volatile!
2. Programmer was despised by his wife in the middle of the night because CAS principle was too simple?
3. The principle of with blocks on ABA | wife incredibly understand again!
4. Cut the finest | 21 picture with you appreciate collection thread unsafe
Thread unsafe ArrayList
There are two types of Collection framework: Map and Collection. Under Collection, there are List, Set and Queue. Below List are ArrayList, Vector, and LinkedList. As shown in the figure below:
Collections are Queue, CopyOnWriteArrayList, CopyOnWriteArraySet, and ConcurrentMap
So let’s look at ArrayList first.
1.1. Low-level initialization of ArrayList
First, let’s review the use of arrayLists. Let’s initialize an ArrayList that contains values of type Integer.
new ArrayList<Integer>();
Copy the code
So what’s being done at the bottom?
1.2. Underlying Principles of ArrayList
1.2.1 Initializing arrays
/** * Constructs an empty list with an initial capacity of ten. */
public ArrayList(a) {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
Copy the code
We created an empty array with a capacity of 0, which according to the official English comment should be 10, but it’s actually 0, and we’ll see why it’s not.
1.2.1 Add Operations for an ArrayList
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
Copy the code
The key is this step: elementData[size++] = e; Size++ and elementData[xx]=e, neither of which is atomic (an indivisible one or series of operations that either execute successfully or none at all).
1.2.2 ArrayList Expands source code parsing
(1) When performing the add operation, we will first confirm whether the size of the array is exceeded
ensureCapacityInternal(size + 1);
Copy the code
(2) Calculate the current capacity of the array: calculateCapacity
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
Copy the code
MinCapacity: The value is 1
ElementData: represents the current array
Let’s first look at the ensureCapacityInternal method called by ensureCapacityInternal
calculateCapacity(elementData, minCapacity)
Copy the code
The method of calculateCapacity is as follows:
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
Copy the code
ElementData: represents the current array. When the first element is added, elementData equals DEFAULTCAPACITY_EMPTY_ELEMENTDATA (empty array)
MinCapacity: equal to 1
DEFAULT_CAPACITY: equal to 10
Math.max(DEFAULT_CAPACITY, minCapacity) = 10
Summary: So the first time you add an element, calculate the size of the array to 10
(3) Determine the current capacity
minCapacity = 10
elementData.length=0
Summary: Since minCapacity > elementData.length, do the first expansion, call the grow() method to expand from 0 to 10.
(4) Call the grow method
OldCapacity = 0, newCapacity = 10.
ElementData = arrays.copyof (elementData, newCapacity);
The current array and the size of the array copy operation, assigned to elementData. The capacity of the array is set to 10
The value of elementData will be different from that of DEFAULTCAPACITY_EMPTY_ELEMENTDATA.
(5) The element is then assigned to the first element of the array, and the size increases by 1
elementData[size++] = e;
Copy the code
(6) When we add the second element, we pass 2 to ensureCapacityInternal
ensureCapacityInternal(size + 1)
Copy the code
Size = 1 + 1 = 2 size
(7) When adding elements for the second time, execute calculateCapacity
The value of elementData is not equal to the value of DEFAULTCAPACITY_EMPTY_ELEMENTDATA, so return 2 directly
(8) When adding an element the second time, execute ensureExplicitCapacity
Since minCapacity is equal to 2, which is less than the current array length of 10, no expansion is performed and the grow method is not executed.
(9) Add the second element to the array, incrementing the size by 1
elementData[size++] = e
Copy the code
(10) The grow method is called when the 11th element is added
MinCapacity =11, elementData.length=10, call the grow method.
(11) Expand by 1.5 times
int newCapacity = oldCapacity + (oldCapacity >> 1);
Copy the code
NewCapacity =10+5=15; newCapacity= 15; oldCapacity=10; newCapacity= 15;
(12) Summary
- 1. Initialize the ArrayList to a
An empty array
- 2. Add to ArrayList is not thread-safe
- 3. When the first element is added to the ArrayList, the array capacity is set to
10
- 4. When the capacity of ArrayList exceeds the current capacity, expand the capacity to
1.5 times
After the first expansion, the capacity is 15, and the second expansion is 22… - 5.ArrayList will copy and call the array after the first and expansion
Arrays.copyOf
Methods.
1.3. Is ArrayList single-threaded environment safe?
Scene:
We show that ArrayList is thread-safe under a single thread with an example of adding building blocks.
Add the building blocks triangle A, quadrilateral B, pentagonal C, hexagon D and pentagram E to A box in turn. There are 5 squares in the box, and each square can put A building block.
Code implementation:
(1) This time we use the new building block class BuildingBlockWithName
This block class can pass shape and name
/** * Building blocks *@author: Wukong chat structure *@create: the 2020-08-27 * /
class BuildingBlockWithName {
String shape;
String name;
public BuildingBlockWithName(String shape, String name) {
this.shape = shape;
this.name = name;
}
@Override
public String toString(a) {
return "BuildingBlockWithName{" + "shape='" + shape + ",name=" + name +'} '; }}Copy the code
(2) Initialize an ArrayList
ArrayList<BuildingBlock> arrayList = new ArrayList<>();
Copy the code
(3) Add triangle A, quadrilateral B, pentagonal C, hexagon D, pentagram E
arrayList.add(new BuildingBlockWithName("Triangle"."A"));
arrayList.add(new BuildingBlockWithName(Quadrilateral."B"));
arrayList.add(new BuildingBlockWithName("Pentagon"."C"));
arrayList.add(new BuildingBlockWithName("Hexagon"."D"));
arrayList.add(new BuildingBlockWithName("Pentacle"."E"));
Copy the code
(4) Verify that the contents and order of the elements in the arrayList are consistent with the addition
BuildingBlockWithName{shape='triangle,name=A} BuildingBlockWithName{shape='Quadrilateral,name=B} BuildingBlockWithName{shape='pentagonal,name=C} BuildingBlockWithName{shape='Hexagon,name=D} BuildingBlockWithName{shape=Name =E}Copy the code
We see that the results do agree.
Summary: ArrayList is thread-safe in a single-threaded environment.
ArrayList is not safe under multiple threads
The scenario is as follows: 20 threads randomly add an arbitrarily shaped building block to the ArrayList.
(1) Code implementation: 20 threads to randomly store a building block in the array.
(2) Print results: after the program starts to run, each thread stores only one random building block.
Will continue to store in the array blocks, multiple threads will compete for an array of storage, in the process of storage, throws an exception: ConcurrentModificationException (parallel) modified
Exception in thread "10" Exception in thread "13" java.util.ConcurrentModificationException
Copy the code
This is common concurrency exception: Java. Util. ConcurrentModificationException
1.5 How to solve the problem of ArrayList thread insecurity?
There are the following plans:
- 1. Vector instead of ArrayList
- 2. Use the Collections. Synchronized (new ArrayList < > ())
- 3.CopyOnWriteArrayList
1.6 Is Vector Thread safe?
Let’s analyze the source code for Vector.
1.6.1 Initializing Vector
Initialize the capacity to 10
public Vector(a) {
this(10);
}
Copy the code
1.6.2 The Add operation is thread safe
The Add method with a synchronized to ensure the Add operation is thread-safe (ensure visibility, atomicity, order), to this a few concepts have don’t understand the articles – may have a look before you counter the interviewer | | 14 zhang diagram is never afraid of being asked a volatile!
1.6.3 Vector doubled
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
Copy the code
Note: capacityIncrement can be passed when initializing; if not, it defaults to 0. If yes, oldCapacity+capacityIncrement is set during the first capacity expansion and is doubled during the second capacity expansion.
Disadvantages: Although guaranteed thread safety, performance degrades due to blocking with the addition of synchronized.
1.6.4 Simulate the add operation of Vector with building blocks
When the elements are stored in the vector, a lock is added to the box, and only one person can store the blocks. After that, the lock is released, and when the second element is placed, the lock is added, and so on.
1.7 the use of the Collections. SynchronizedList ensure thread safety
We can use the Collections. SynchronizedList method is to encapsulate an ArrayList.
List<Object> arrayList = Collections.synchronizedList(new ArrayList<>());
Copy the code
Why is it thread-safe when it’s encapsulated?
Source code parsing: Because Collections. SynchronizedList encapsulation after the list, the list of all the operation method is with synchronized keyword (with the exception of the iterator ()), the equivalent of all operations are locked, So it is thread-safe to use it (except to iterate over arrays).
Note: Manual synchronization is required when iterating over arrays. The official example is as follows:
synchronized (list) {
Iterator i = list.iterator(); // Must be in synchronized block
while (i.hasNext())
foo(i.next());
}
Copy the code
1.8 Use CopyOnWriteArrayList to ensure thread safety
, version 1.8.1 CopyOnWriteArrayList thought
- Copy on write: An idea that separates read and write.
- Write operation: When adding an element to the current array, it does not directly add the element to the current array. Instead, it copies the array first. After adding the element to the new array, it points the reference to the new array. Since arrays are modified with the volatile keyword, other threads can immediately know when the array is reassigned.
- Read operation: when reading an array, read the old array without locking.
- Read/write separation: a write operation copies a new array, and a read operation reads the old array, so it is read/write separation.
1.8.2 Usage Mode
CopyOnWriteArrayList<BuildingBlockWithName> arrayList = new CopyOnWriteArrayList<>();
Copy the code
1.8.3 Underlying source analysis
The process of ADD:
- A reentrant lock is defined
ReentrantLock
- Before adding an element, acquire the lock
lock.lock()
- To add an element, copy the current array
Arrays.copyOf
- When adding an element, expand by +1 (
len + 1
) - After the element is added, the array reference points to the array with the newly added element
setArray(newElements)
Why do other threads immediately know when an array is reassigned?
Because the array here is volatile. Wow, volatile again. That’s a nice keyword
private transient volatile Object[] array;
Copy the code
1.8.4 Differences between ReentrantLock and synchronized
To highlight
Similarities:
- 1. Both are used to coordinate multi-threaded access to shared objects and variables
- 2. Both locks are reentrant. The same thread can obtain the same lock multiple times
- 3. Both guarantee visibility and mutual exclusion
Difference:
- 1.ReentrantLock acquires and releases the lock, and synchronized implicitly acquires and releases the lock
- 2.ReentrantLock can respond to interrupts, synchronized can’t, providing more flexibility in dealing with lock unavailability
- 3.ReentrantLock is API level and synchronized is JVM level
- 4.ReentrantLock can implement fair and non-fair locking
- 5.ReentrantLock can bind multiple conditions through the Condition
- 6. The underlying implementation is different. Synchronized refers to synchronous blocking and uses a pessimistic concurrency policy, while lock refers to synchronous non-blocking and uses an optimistic concurrency policy
1.8.5 The difference between Lock and synchronized
- 1.Lock The Lock must be obtained and released manually. It’s like the difference between automatic and manual
- Lock is an interface, synchronized is a keyword in Java, and synchronized is a built-in language implementation.
- 2. Synchronized releases locks held by the thread when an exception occurs, so deadlocks do not occur. UnLock () : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock() : unLock()
- 3. A Lock can interrupt a thread waiting for a Lock, but synchronized cannot. With synchronized, a thread waiting for a Lock can wait forever and cannot respond to an interrupt.
- 4. You can tell whether a Lock has been successfully acquired by locking, whereas synchronized cannot.
- 5.Lock Can improve the efficiency of read operations by multiple threads by implementing read/write locks.
Thread unsafe Hashsets
Now that we’ve spent a lot of time talking about arrayLists being thread-safe, and how we can use other ways to make them thread-safe, hashSets should be a little easier to understand.
2.1 Usage of HashSet
The usage is as follows:
Set<BuildingBlockWithName> Set = new HashSet<>();
set.add("a");
Copy the code
Initial capacity =10, load factor =0.75 (start expansion when the number of elements reaches 75% of capacity)
2.2 Underlying Principles of HashSet
public HashSet() {
map = new HashMap<>();
}
Copy the code
The underlying use is again HashMap().
The add operation of a HashSet requires only one parameter (value) and the add operation of a HashMap requires two parameters (key and value).
2.3 Add operations for a HashSet
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
Copy the code
In the add operation of a HashSet, the key is equal to the value passed. The value is PRESENT, and the PRESENT is the new Object(). Key =e, value=new Object The Hash only cares about the key, not the value.
Why HashSets are unsafe: The underlying ADD operation does not guarantee visibility, atomicity. So it’s not thread safe.
2.4 How can I Ensure Thread Safety
-
1. The use of the Collections. SynchronizedSet
Set<BuildingBlockWithName> set = Collections.synchronizedSet(new HashSet<>()); Copy the code
-
2. Use CopyOnWriteArraySet
CopyOnWriteArraySet<BuildingBlockWithName> set = new CopyOnWriteArraySet<>(); Copy the code
2.5 The underlying CopyOnWriteArraySet is CopyOnWriteArrayList
public CopyOnWriteArraySet(a) {
al = new CopyOnWriteArrayList<E>();
}
Copy the code
Thread unsafe HashMap
3.1 Use of HashMap
Similarly, a HashMap, like a HashSet, is thread unsafe in a multi-threaded environment.
Map<String, BuildingBlockWithName> map = new HashMap<>();
map.put("A".new BuildingBlockWithName("Triangle"."A"));
Copy the code
3.2 Solution to HashMap Thread Insecurity:
- 1.Collections.synchronizedMap
Map<String, BuildingBlockWithName> map2 = Collections.synchronizedMap(new HashMap<>());
Copy the code
- 2.ConcurrentHashMap
ConcurrentHashMap<String, BuildingBlockWithName> set3 = new ConcurrentHashMap<>();
Copy the code
3.3 ConcurrentHashMap principle
ConcurrentHashMap, which internally subdivides a number of small HashMaps, called segments. By default, a ConcurrentHashMap is further subdivided into 16 segments, which is the concurrency of the lock. If you need to add a new entry to ConcurrentHashMap, you do not lock the entire HashMap. Instead, you first find out which segment the entry should be stored in according to the Hashcode, then lock the segment, and complete the PUT operation. In a multi-threaded environment, if multiple threads perform the PUT operation at the same time, the threads can be truly parallel as long as the entries to be added are not stored in the same segment.
Other collection classes
LinkedList: Thread unsafe, same as ArrayList TreeSet: thread unsafe, same as HashSet LinkedHashSet: thread unsafe, same as HashSet TreeMap: HashMap: thread unsafe HashTable: thread safe
conclusion
The first part of this article detailed the underlying scaling principles of the ArrayList collection, demonstrating that ArrayList thread insecurity causes concurrent modification exceptions to be thrown. Then, through source code parsing, we explain three ways to ensure thread safety:
Vector
Through theadd
And so onsynchronized
To be thread safeCollections.synchronized()
By wrapping an array, we preappend the operation methods on the arraysynchronized
To be thread safeCopyOnWriteArrayList
throughWhen writing copy
To be thread safe.
Part 2 explained that hashsets are thread unsafe, and there are two ways to make them thread-safe:
- Collections.synchronizedSet
- CopyOnWriteArraySet
Part 3 explains that HashMap is thread unsafe. There are two ways to ensure thread safety:
- Collections.synchronizedMap
- ConcurrentHashMap
In addition, the differences between ReentrantLock and synchronized and between Lock and synchronized are compared in detail in the process of explanation.
Easter Egg: If you’re smart enough, there’s one more important thing missing from the set: the Queue. Looking forward to what happens next?
White go whoring? Forward -> look -> Like – favorites!!
I am Wukong, a code to become stronger! I’m gonna be a super Saiyan!
In addition, you can search wechat “Wukong chat architecture” or PassJava666, progress together! On my GitHub home page, follow my Spring Cloud practical project, Jibbi Pass.