Functions and application scenarios of ThreadLocal
ThreadLocal is a kind of concurrent container, because it is composed of ThreadLocalMap. ThreadLocal is designed to solve the problem that variables cannot be shared by multiple threads. The difference between ThreadLocal and Lock and Synchronized is that a ThreadLocal assigns a variable (object) to each thread, and each thread has a copy of the variable. In this way, each thread uses its own (variable) object instance to isolate threads from each other. Lock and Synchronized are ways of sequential execution of threads. Here’s a simple example: At present, there are 100 students waiting to sign, but the teacher only has one pen. The teacher can only distribute the pen to each student in order, wait for student A to sign and then hand it to student B, which is similar to the way of Lock and Synchronized. With ThreadLocal, the teacher just takes out a hundred pens and gives them to each student; More efficient colleagues also pay a memory cost; That’s the idea of space for time
Usage scenarios
-
Spring’s transaction isolation is handled using ThreadLocal and AOP; Mainly TransactionSynchronizationManager this class;
-
Solve the SimpleDateFormat thread security problem;
When we use SimpleDateFormat’s parse() method, the parse() method calls calendar.clear () and then calendar.add (). If a program calls add() first, Another thread then calls the clear() method; The parse() method then gets a parsing error; If you don’t believe me, here’s an example:
public class SimpleDateFormatTest { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); public static void main(String[] args) { for (int i = 0; i < 50; i++) { Thread thread = new Thread(new Runnable() { @Override public void run(a) { dateFormat(); }}); thread.start(); }}/** * string is converted to date type */ public static void dateFormat(a) { try { simpleDateFormat.parse("2021-5-27"); } catch(ParseException e) { e.printStackTrace(); }}}Copy the code
In this case, we only started 50 threads and the problem started, and it turns out that sometimes we only had 10 threads and things went wrong;
Exception in thread "Thread-40" java.lang.NumberFormatException: For input string: "" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:601) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2084) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at cn.haoxy.use.lock.sdf.SimpleDateFormatTest.dateFormat(SimpleDateFormatTest.java:36) at cn.haoxy.use.lock.sdf.SimpleDateFormatTest$1.run(SimpleDateFormatTest.java:23) at java.lang.Thread.run(Thread.java:748) Exception in thread "Thread-43" java.lang.NumberFormatException: multiple points at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1890) at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110) at java.lang.Double.parseDouble(Double.java:538) at java.text.DigitList.getDouble(DigitList.java:169) at java.text.DecimalFormat.parse(DecimalFormat.java:2089) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at ............. Copy the code
To solve this problem, let each thread new its own SimpleDateFormat, but what if 100 threads new100 SimpleDateFormat? Of course we can’t do that, we can solve this problem with thread pools plus ThreadLocal;
public class SimpleDateFormatTest { private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>() { @Override // Initialize thread-local variables protected SimpleDateFormat initialValue(a) { return new SimpleDateFormat("yyyy-MM-dd"); }};public static void main(String[] args) { ExecutorService es = Executors.newCachedThreadPool(); for (int i = 0; i < 500; i++) { es.execute(() -> { // Call the string to date method dateFormat(); }); } es.shutdown(); } /** * string is converted to date type */ public static void dateFormat(a) { try { // Get () in ThreadLocal local.get().parse("2021-5-27"); } catch(ParseException e) { e.printStackTrace(); }}}Copy the code
This elegantly solves the thread-safety problem;
-
Solve the problem of excessive parameter transfer; For example, a method calls several methods, and each method needs to pass parameters. For example:
void work(User user) { getInfo(user); checkInfo(user); setSomeThing(user); log(user); } Copy the code
With ThreadLocal:
public class ThreadLocalStu { private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>(); void work(User user) { try { userThreadLocal.set(user); getInfo(); checkInfo(); someThing(); } finally{ userThreadLocal.remove(); }}void setInfo(a) { User u = userThreadLocal.get(); / /... } void checkInfo(a) { User u = userThreadLocal.get(); //.... } void someThing(a) { User u = userThreadLocal.get(); //....}}Copy the code
Global variables need to be saved in each thread (for example, user information is stored in ThreadLocal after successful login, and then the business logic of the current thread operation is directly get to complete, effectively avoiding the trouble of passing parameters back and forth), reducing the code coupling degree at a certain level.
- For example, store transaction IDS and other information. Each thread is private.
- For example, aop logging requires before to record the request ID and end to retrieve the request ID, which is also possible.
- For example, JDBC connection pooling (a typical one
ThreadLocal
Usage) - . And so on…
The principle of analysis
So we’ve basically seen how ThreadLocal can be used and how it can be used, but there’s more to it than that. Now let’s analyze its principle;
Let’s first look at its set() method;
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
}
Copy the code
If the map is null, the current thread is used as the key, and the value passed in as the map value. If null, create a ThreadLocalMap and place the key and value in it. From here you can see that the value is stored in ThreadLocalMap;
So let’s see where ThreadLocalMap comes from. Okay? Let’s look at the getMap() method:
// The threadLocals variable is maintained in the Thread class
ThreadLocal.ThreadLocalMap threadLocals = null;
// The getMap() method in ThreadLocal
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
This explains why there is a ThreadLocalMap in each Thread, because ThreadLocalMap references are maintained in the Thread; This ensures isolation between threads;
Let’s go back to the set() method and see that createMap(t, value) is created when map is empty;
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
Copy the code
A new ThreadLocalMap is then assigned to the threadLocals member variable; ThreadLocalMap constructor:
ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize an Entry
table = new Entry[INITIAL_CAPACITY];
// Calculate where the key should be stored
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// Put Entry in the specified position
table[i] = new Entry(firstKey, firstValue);
size = 1;
// Set the array size to 16*2/3=10, similar to 0.75*16=12 in HashMap
setThreshold(INITIAL_CAPACITY);
}
Copy the code
Here’s a general idea, and I’ll explain the internal structure of ThreadLocalMap in more detail later;
Let’s look at the get() method again:
public T get(a) {
Thread t = Thread.currentThread();
// Use the current thread as the key to get ThreadLocalMap
ThreadLocalMap map = getMap(t);
if(map ! =null) {
// Map is not empty, and then get the Entry in the map
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
// If Entry is not empty, get the corresponding value
T result = (T)e.value;
returnresult; }}// Initialize with this method if map or entry is empty and return the value of the method
return setInitialValue();
}
Copy the code
The get() and set() methods are easier to understand, but if map equals empty or entry equals empty let’s see what setInitialValue() does:
private T setInitialValue(a) {
// The subclass implements the value and initializes the variable
T value = initialValue();
Thread t = Thread.currentThread();
// again getMap();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
// and set()
createMap(t, value);
return value;
}
Copy the code
Let’s look again at the initialValue() method in ThreadLocal;
protected T initialValue(a) {
return null;
}
Copy the code
Set the initial value, which is implemented by subclasses; As in our example above, override the initialValue() method in ThreadLocal:
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal<SimpleDateFormat>() {
@Override
// Initialize thread-local variables
protected SimpleDateFormat initialValue(a) {
return new SimpleDateFormat("yyyy-MM-dd"); }};Copy the code
The createMap() method is the same as the createMap() method in the set() method. That leaves the removve() method
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
//2. Delete the key-value pair with the current threadLocal instance as key from the map
m.remove(this);
}
Copy the code
Let’s take a look at the underlying structure of ThreadLocalMap
The underlying structure of ThreadLocalMap
We’ve seen ThreadLocal usage scenarios and some of its more important methods; Now let’s go to its internal structure; The data is stored in the internal ThreadLocalMap class of ThreadLocal. ThreadLocalMap maintains an Entry object, which means that data is ultimately stored in the Entry object.
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<? >>{
/** The value associated with this ThreadLocal. */Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; } } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) { table =new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/ /...
}
Copy the code
The construction method of Entry is to store the current thread as key and the variable value Object as value. Entry is also included in the ThreadLocalMap constructor in the source code above; See that Entry is an array; The initial length is INITIAL_CAPACITY = 16. Because Entry inherits WeakReference, calling the super(k) method in the Entry constructor wraps the threadLocal instance as a WeakReferenece. This is why ThreadLocal can leak memory;
Causes of memory leaks
There is a chain of references as shown: Thread Ref->Thread->ThreadLocalMap->Entry->Key:Value That is, reference objects are recycled whether memory is weak enough or not; Weak references have a short lifetime; When a GC occurs it will look like this:
When an Entry with a null Key appears in TreadLocalMap, the value of the Entry with a null Key cannot be accessed. If the thread does not end late (that is, the reference chain exists meaningless all the time), the value can never be reclaimed, resulting in memory leakage. If the current Thread runs out of Thread, ThreadLocalMap, and Entry without a reference chain, it will be collected during garbage collection. But we all use thread pools in development, and the reuse of thread pools doesn’t end voluntarily; So there will still be memory leaks;
The solution is also very simple, is after using the active call remove() method release;
Resolving Hash Conflicts
I remember that I learned many methods to resolve hash conflicts when I studied data structure in college. Such as:
-
Linear detection method (one kind of open address method) : if the computed hash address is occupied, the next space is searched in order. If you can’t find an empty place at the end, start looking again.
-
Secondary detection method (a type of open address method)
- Linked address method: Linked address is a single linked list for each synonym to resolve conflicts. HashMap adopts this method.
-
Multiple Hash: In the case of key conflicts, multiple hashes are used until no conflicts occur. This method is not easy to accumulate but requires a large amount of computation.
-
Public overflow area method, this method needs two tables, one store basic data, the other store conflict data called overflow table;
The pictures above are some of the information found on the Internet, and the university when learning almost I directly used; Also when their review again;
Given all the ways to resolve Hash collisions, which method does ThreadLocalMap use? We can take a look at the source code:
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
// Calculate the location of the array according to the length of HashCode &
int i = key.threadLocalHashCode & (len-1);
// Iterate over the elements in the Entry array
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If the key of the Entry object happens to be the key to be set, refresh the value in the Entry;
if (k == key) {
e.value = value;
return;
}
// entry! =null,key==null, threadLcoal's key has been GC
// There will be memory leaks, of course the author also knows this situation exists, so here is a judgment to solve the dirty
// Entry (you don't want to have outdated entries in the array), but it doesn't solve the leak problem because the old value still exists
if (k == null) {
// Replace the "dirty" entry with a null key with the currently inserted value
replaceStaleEntry(key, value, i);
return; }}// Create an entry and insert it at I in the table
tab[i] = new Entry(key, value);
int sz = ++size;
if(! cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }Copy the code
From this we can see that linear probing is used to resolve hash collisions!
NextIndex (I, len) : ((I + 1 < len)? i + 1 : 0); , that is, continue linear detection until you find an empty position, when you get to the end of the hash table has not found the empty position and then start looking from 0, into a circle!
Where do objects exist when ThreadLocal is used?
In Java, stack memory is owned by a single thread. Each thread has one stack memory, and its variables are visible only to the thread it belongs to. That is, stack memory can be understood as a thread’s private variables, while variables in heap memory are visible to all threads and can be accessed by all threads! Are instances of ThreadLocal and its values stored on the stack? This is not true, because instances of ThreadLocal are actually held by the class they create (at the top, by the thread), and ThreadLocal values are actually held by the thread instance. They are all on the heap, but with some tricks to make them visible to the thread.
So much for ThreadLocal, which I hope will help you; As long as the effort to solve the matter is not difficult;