ThreadLocal solves the multi-threaded security case
The date utility class wrapped in the project is not working in a multithreaded environment. Let’s see how it works
public class ThreadLocalTest {
public static void main(String[] args) {
// Create a thread pool
ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20.20.0L,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(1024), threadFactory);
for (int i = 0; i < 20; i++) {
threadPoolExecutor.execute(
()-> System.out.println(DateUtilSafe.parse("The 2019-06-01 16:34:30"))); } threadPoolExecutor.shutdown(); }}Copy the code
Date utility classes (thread unsafe)
public class DateUtilNotSafe {
private static final SimpleDateFormat sdf =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static Date parse(String dateStr) {
Date date = null;
try {
date = sdf.parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
returndate; }}Copy the code
Multithreading error screenshots:
ThreadLocal solution:
public class DateUtilSafe {
private static final ThreadLocal<DateFormat> THREAD_LOCAL = ThreadLocal.withInitial(
() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));public static Date parse(String dateStr) {
Date date = null;
try {
date = THREAD_LOCAL.get().parse(dateStr);
} catch (ParseException e) {
e.printStackTrace();
}
returndate; }}Copy the code
Analysis:
The SimpleDateFormat(SDF) class has a Calendar object reference inside it that stores date information associated with the SDF, Methods such as sdf.parse(dateStr), sdf.format(date), etc. pass date-related strings, dates, etc., which are stored by dating Calendar references. This causes a problem. If your SDF is static, multiple threads will share the SDF and Calendar reference, and look at the sdf.parse() method. Atomicity is not guaranteed in the parse method, so there are thread safety issues:
Date parse(a) {
calendar.clear(); / / clean up the calendar.// Perform some operations, set the calendar date, etc
calendar.getTime(); // Get the calendar time
}
Copy the code
Since SimpleDateFormat is being shared by multiple threads, let’s leave it unshared and each thread stores its own SimpleDateFormat object. Play with your own objects and there will be no threading problems. ThreadLocal lets threads keep separate copies of their own variables. Each thread uses its own copy of variables independently, without affecting copies of variables in other threads.
ThreadLocal profile
Many people think that ThreadLocal is a kind of multi-threaded synchronization mechanism. In fact, it is not a solution for variable thread safety in multi-threaded environment. It is to solve the security problem of member variables in multi-threaded environment, not to solve the security problem of shared variables in multi-threaded environment.
Thread synchronization allows multiple threads to share a variable, whereas ThreadLocal allows each thread to create its own copy of a variable, so each thread can change its own copy of a variable independently. And does not affect variable copies of other threads.
ThreadLocalMap
ThreadLocal has a very important inner class: The internal structure of a ThreadLocalMap is similar to that of a Map, consisting of an Entry consisting of a key and value pair. The key is the ThreadLocal itself, and the value is a copy of the corresponding thread variable
Note:
ThreadLocal itself does not store values; it simply provides you with a key that finds the value.
2, ThreadLocal is contained in Thread, not Thread contained in ThreadLocal.
ThreadLocalMap and HashMap function similarly, but the implementation is quite different:
- The data structure of a HashMap is an array + linked list
- The data structure of ThreadLocalMap is simply an array
- HashMap resolves hash conflicts by using chained addresses
- ThreadLocalMap resolves hash collisions using an open address method
- All references to the Entry inner class in a HashMap are strong references
- The key in the Entry inner class of ThreadLocalMap is a weak reference and the value is a strong reference
Chain address method
The basic idea of this method is to form a single linked list called synonym chain of all elements whose hash address is I, and store the head pointer of the single linked list in the ith element of the hash table, so the search, insertion and deletion are mainly carried out in the synonym chain.
Open address method
The basic idea of this approach is to find the next empty hash address whenever a conflict occurs (this is very important, the source code is based on this feature, must understand here to proceed), as long as the hash table is large enough, the empty hash address will always be found and the record will be saved.
Advantages and disadvantages of chain address method and open address method
Open address method:
- Prone to stacking problems, not suitable for large-scale data storage.
- The design of hash functions has a great influence on collisions, and many collisions may occur during insertion.
- The deleted element is one of several conflicting elements, which needs to be processed for the following elements, so the implementation is more complicated.
Chain address method:
- It is simple to deal with conflicts, and there is no accumulation phenomenon, and the average search length is short.
- Nodes in linked lists are applied dynamically, which is suitable for cases where the construction table cannot determine the length.
- Deleting nodes is easy to implement. Simply delete the corresponding nodes on the linked list.
- Pointers need extra space, so when the node size is small, the open addressing method is more space-saving.
ThreadLocalMap uses the open address method
- ThreadLocal HASH_INCREMENT = 0x61C88647 0x61C88647 is a magic number that allows hash codes to be evenly distributed in a 2 ^ N array, that is, Entry[] table, Google has a lot to explain about this magic number, which I won’t repeat here
- The amount of data stored in ThreadLocal is usually not very large (and the key is weak reference and will be garbage collected to reduce the amount of data in time). In this case, the simple structure of open address method can save more space, and the query efficiency of array is also very high. In addition, the guarantee of the first point, the conflict probability is also low
Relationships among threads, ThreadLocal, and ThreadLocalMap
From the structure diagram above, we can see the core mechanism of ThreadLocal:
Each Thread has a Map inside it. The Map inside a Thread is maintained by a ThreadLocal, which is responsible for fetching and setting Thread variable values from the Map. Therefore, for different threads, each time they obtain the duplicate value, other threads cannot obtain the duplicate value of the current thread, which forms the isolation of the duplicate and does not interfere with each other.
The source code interpretation
Let’s take a look at a few methods provided by the ThreadLocal class:
public T get(a) {}public void set(T value) {}public void remove(a) {}protected T initialValue(a) {}Copy the code
The get() method is used to get a copy of the variables that ThreadLocal keeps in the current thread. Set () is used to set a copy of a variable in the current thread. Remove () is used to remove copies of variables in the current thread. InitialValue () is a protected method that is usually overridden when used
The get method
// Get the value by key
public T get(a) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the ThreadLocalMap of the current thread
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// This is the current ThreadLocalMap (key). GetEntry gets value: e from the key
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
// Return the value obtained
returnresult; }}return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
Set method
public void set(T value) {
// Get the current thread
Thread t = Thread.currentThread();
// Get the ThreadLocalMap of the current thread
ThreadLocalMap map = getMap(t);
if(map ! =null)
// re-place ThreadLocal and the new value copy into the map.
map.set(this, value);
else
/ / create
createMap(t, value);
}
// Create a ThreadLocalMap and bind ThreadLocalMap to Thread
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
The remove method
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
// Call the remove method in ThreadLocalMap
m.remove(this);
}
Copy the code
ThreadLocalMap (ThreadLocalMap, ThreadLocalMap, ThreadLocalMap, ThreadLocalMap, ThreadLocalMap); See the analysis of ThreadLocalMap below.
ThreadLocalMap data structure
public class ThreadLocal<T> {
// The data structure uses array + open address method
static class ThreadLocalMap {
private Entry[] table;
// Entry inherits WeakReference,
// There are memory leaks in this block, which will be explained later
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value of the ThreadLocal key is value */
Object value;
// The inner class Entry is a key and value structure similar to the map structure
// Key is a ThreadLocal and value is a copy of the variable valueEntry(ThreadLocal<? > k, Object v) {super(k); value = v; }}}}Copy the code
Set method
ThreadLocalMap Sets the key and value
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
// Calculate the index of key
int i = key.threadLocalHashCode & (len-1);
for(Entry e = tab[i]; e ! =null;
e = tab[i = nextIndex(i, len)]) {
// Get the key for this loopThreadLocal<? > k = e.get();// Index value calculated by key
// An Entry whose first Key is empty after a linear search
if (k == key) {
e.value = value;
return;
}
// if k == null &&e! = null, indicating that k is recycled,
// because Entry inherits WeakReference WeakReference, GC will recycle the key
if (k == null) {
// The new key and value can be placed in the position that is no longer used
replaceStaleEntry(key, value, i);
return; }}// If the method does not return in the above method
// The Entry of position I is empty. You can set key and value
tab[i] = new Entry(key, value);
int sz = ++size;
The cleanSomeSlots method returns false to indicate that there are no more entries in the array with empty keys that need to be cleared
// The array is full, and sz means the number of elements in the array is greater than the threshold
// You need to call rehash for expansion
if(! cleanSomeSlots(i, sz) && sz >= threshold)/ / capacity
rehash();
}
Copy the code
ReplaceStaleEntry Replacement method
private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
// Clear the starting position of the element.
int slotToExpunge = staleSlot;
// Traverse forward until Entry is empty
for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
i = prevIndex(i, len))
if (e.get() == null)
// Record the index position where the last key is null
slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
// occurs first
// Iterate backwards until Entry is empty
for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// If the Entry key is the same as the incoming key, the incoming value is replaced with the Entry value
if (k == key) {
e.value = value;
// Switch the I and staleSlot elements (staleSlot is the first element to be cleared)
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
// if it is equal, it means that the above traversal is not found, and the key is null.
SlotToExpunge = I, slotToExpunge = I
// Since the original staleSlot element has been placed in position I, there is no need to clear the elements before position I
if (slotToExpunge == staleSlot)
slotToExpunge = i;
// Clear entries with empty keys from slotToExpunge
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
// If the first traversal of an element with a null key is not found, the first traversal of an element with a null key is not found.
// Set slotToExpunge to the current location
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
// If the key is not found, create an Entry and place it in the staleSlot location
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
/ / if slotToExpunge! =staleSlot, indicating that there are other positions in addition to the staleSlot position that need to be cleared
// Definitions to clear: Entries with a null key. Call cleanSomeSlots to clear entries with a null key
if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code
CleanSomeSlots cleanup method
private boolean cleanSomeSlots(int i, int n) {
boolean removed = false;
Entry[] tab = table;
int len = tab.length;
do {
// Next index location
i = nextIndex(i, len);
Entry e = tab[i];
// Iterate over the element whose key is null
if(e ! =null && e.get() == null) {
// reset n
n = len;
// Indicates a remove element
removed = true;
// Remove the element whose key is null after the I positioni = expungeStaleEntry(i); }}while ( (n >>>= 1) != 0);
return removed;
}
Copy the code
The get () method
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// Call the getEntry method and get the corresponding Entry through this (ThreadLocal that calls the get() method)
ThreadLocalMap.Entry e = map.getEntry(this);
// If Entry is not empty, the destination Entry is found and its value is returned
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}// If the thread's ThreadLocalMap is empty or no target Entry is found, the setInitialValue method is called
return setInitialValue();
}
Copy the code
SetInitialValue method
private T setInitialValue(a) {
// Default null, need to override this method,
T value = initialValue();
// The current thread
Thread t = Thread.currentThread();
// Get the threadLocals of the current thread
ThreadLocalMap map = getMap(t);
// if threadLocals is not empty, the current ThreadLocal will be used as the key
// null is inserted as value into ThreadLocalMap
if(map ! =null)
map.set(this, value);
// If threadLocals is empty, create a ThreadLocalMap
// Create a new Entry into the ThreadLocalMap
// Call ThreadLocal and value of the set method as the key and value of this Entry
else
createMap(t, value);
return value;
}
Copy the code
The getEntry method
private Entry getEntry(ThreadLocal
key) {
// Compute the index position according to the hash code
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
// If the Entry key is equal to the incoming key, it is the destination Entry and is returned directly
if(e ! =null && e.get() == key)
return e;
// Otherwise, e is not the target Entry, and the search continues after e
else
return getEntryAfterMiss(key, i, e);
}
Copy the code
GetEntryAfterMiss method
private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while(e ! =null) { ThreadLocal<? > k = e.get();// Find the destination Entry and return directly
if (k == key)
return e;
// Call expungeStaleEntry to remove elements whose key is null
if (k == null)
expungeStaleEntry(i);
else
// Next index location
i = nextIndex(i, len);
// Entry for the next iteration
e = tab[i];
}
// Return null if not found
return null;
}
Copy the code
Remove () method
public void remove(a) {
// Get the ThreadLocalMap of the current thread
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
// Call ThreadLocal of this method as an input and call remove method
m.remove(this);
}
private void remove(ThreadLocal
key) {
Entry[] tab = table;
int len = tab.length;
// Calculate the index position of the current ThreadLocal based on hashCode
int i = key.threadLocalHashCode & (len-1);
// Iterates from position I until Entry is null
for(Entry e = tab[i]; e ! =null;
e = tab[i = nextIndex(i, len)]) {
// find the same key
if (e.get() == key) {
// Calls the clear method, which clears references to key
e.clear();
// Call the expungeStaleEntry method to clear the Entry whose key is null
expungeStaleEntry(i);
return; }}}Copy the code
ExpungeStaleEntry method
// Starting from staleSlot, clear the Entry with an empty key,
// The non-empty element is placed in the appropriate position, and the position with empty Entry is returned
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
// Empty the object in the staleSlot position on TAB
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
// Rehash until we encounter null
Entry e;
int i;
// Iterate over the next element at position (I +1)%len
for (i = nextIndex(staleSlot, len);
// When Entry is empty, exit the loop and return the index position(e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();// If the key of the current Entry traversal is empty, the object in that position will be emptied
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
// Recalculate the index position of the Entry
int h = k.threadLocalHashCode & (len - 1);
// If the index position is not the current index position I
if(h ! = i) {// Empty the I position object to find the correct position for the current Entry
tab[i] = null;
// If the h position is not null, the current Entry position is searched backwards
while(tab[h] ! =null) h = nextIndex(h, len); tab[h] = e; }}}return i;
}
Copy the code
Rehash method
private void rehash(a) {
// Call the expungeStaleEntries method to clean up entries with empty keys
expungeStaleEntries();
// If the size exceeds 3/4 of the threshold, expand the capacity
if (size >= threshold - threshold / 4)
resize();
}
/** * Double the capacity of the table. */
private void resize(a) {
Entry[] oldTab = table;
int oldLen = oldTab.length;
// The new table is twice as long as the old one
int newLen = oldLen * 2;
// Create a new table
Entry[] newTab = new Entry[newLen];
int count = 0;
for (int j = 0; j < oldLen; ++j) {
// Get the Entry of the corresponding position
Entry e = oldTab[j];
if(e ! =null) { ThreadLocal<? > k = e.get();// If the key is null, empty the value
if (k == null) {
e.value = null; // Help the GC
} else {
// Compute the index position of the new table using hash code
int h = k.threadLocalHashCode & (newLen - 1);
// If the new table already has elements at that position, the nextIndex method is called until an empty position is found
while(newTab[h] ! =null)
h = nextIndex(h, newLen);
// Put the element in its placenewTab[h] = e; count++; }}}// Set the capacity expansion threshold for the new table
setThreshold(newLen);
/ / update the size
size = count;
// table points to the new table
table = newTab;
}
Copy the code
Memory leak problem:
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}Copy the code
If a ThreadLocal has no external strong reference to it, the ThreadLocal will be reclaimed the next time the system GC occurs. Null key entries appear in ThreadLocalMap, and there is no way to access the values of these null key entries.
The get, set, and remove methods introduced above all clear the Entry with the null key (expungeStaleEntry method clears the value of the Entry, and these entries will be completely recycled in the next garbage collection).
However, if the current thread is running and the get, set, and remove methods are not executed, the value of the Entry with a null key will always have a strong reference: Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value. As a result, the value of the Entry whose key is null cannot be reclaimed, causing memory leakage.
How do I avoid memory leaks? To avoid this, we can manually call the remove method after using ThreadLocal to avoid memory leaks.
Follow wechat public account: IT elder brother
Java Foundation, Java Web, JavaEE, including Spring Boot, etc
Reply: Resume template, you can get 100 beautiful resumes
Reply: Java learning route, you can get the latest and most complete a learning roadmap
Re: Java ebooks, get 13 must-read books for top programmers