preface
ThreadLocal can be seen in many places in Java concurrent programming, where it is used to address thread safety, that is, to implement multithreaded isolation. Today, the author mainly analyzes the learning process of Threadlocal.
What is ThreadLocal?
To understand how this thing works, you have to know what it is, right?
A ThreadLocal is called a thread variable, meaning that the variable filled with a ThreadLocal belongs to the current thread and is isolated from other threads, meaning that the variable is unique to the current thread.
ThreadLocal creates a copy of a variable in each thread, so that each thread can access its own internal copy of the variable.
With the basic concept in mind, let’s look at an example:
public class Demo {
// Define a global ThreadLocal variable
public static final ThreadLocal<String> STRING_THREAD_LOCAL = ThreadLocal.withInitial(() -> "HELLO");
public static void main(String[] args) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + "-->start:" +STRING_THREAD_LOCAL.get());
Thread t1 = new Thread(() -> {
String value = STRING_THREAD_LOCAL.get();
if(Objects.nonNull(value) && value.equals("HELLO")){
STRING_THREAD_LOCAL.set("WORLD");
}
System.out.println(Thread.currentThread().getName() + "-- >" + STRING_THREAD_LOCAL.get());
},"t1");
t1.start();
t1.join();
System.out.println(Thread.currentThread().getName() + "-->end:"+STRING_THREAD_LOCAL.get()); }}Copy the code
- Let’s take a look at the results:
You can see that the value of the main thread has not changed, while the value of thread T1 has changed.
2. Analysis of ThreadLocal principle
Now that we’ve seen from the examples above, threadLocal can isolate multiple threads so that each thread’s copy of the shared variable changes is visible only to itself. Resolved the issue of sharing between threads
2.1 Method Analysis
If you take a look at the official set source code, you’ll find a key one: ThreadLocalMap
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
It seems that before we get to the set method, we need to look at ThreadLocalMap
2.2 ThreadLocalMap
ThreadLocalMap is actually a member variable maintained by each thread.
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;
}
/ /...
}
Copy the code
ThreadLocalMap contains an array of entries that inherit weak references.
The key of a ThreadLocalMap is a weak reference to the instance of ThreadLocal, and the value is the initial value of the ThreadLocal or the value set by the thread.
2.3 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
The set method simply sets a value in the current thread and stores it in ThreadLocalMap.
The createMap method initializes the Map and sets its value while getMap is empty
- ThreadLocalMap is not initialized
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize a table array of length 16
table = new Entry[INITIAL_CAPACITY];
// Calculate index subscript through routing algorithm
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
Copy the code
- ThreadLocalMap has been initialized
If it has already been initialized, call threadLocalmap.set () and keep it.
private void set(ThreadLocal
key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
// Iterate through the Entry array based on index indices
for(Entry e = tab[i]; e ! =null; e = tab[i = nextIndex(i, len)]) { ThreadLocal<? > k = e.get();// If the key is equal, the original value is overwritten
if (k == key) {
e.value = value;
return;
}
// If key is empty, overwrite key and value.
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// If the threshold is exceeded, expand the capacity
if(! cleanSomeSlots(i, sz) && sz >= threshold)//for loop, rehash();
rehash();
}
Copy the code
2.4 Hash Conflict in ThreadLocalMap
ThreadLocalMap’s set method is similar to HashMap’s PUT method. But there was also a problem:
ThreadLocalMap does not have a list structure, so how can hash conflicts be resolved?
- Calculates the index subscript I of the current element store based on the key.
- tab[i] ! = null
- If the key stored in TAB [I] is different from the current key, search for the empty key
- If TAB [I] stores the same keys but different values, the values are updated
- If TAB [I] holds a null key, the ThreadLocal instance may have been reclaimed.
- If TAB [I] == null, store the key and value directly.
In plain English, this is a linear for loop that iterates over empty positions to resolve hash conflicts.
2.5 Replace and clear the replaceStaleEntry method
When key==null, the replaceStaleEntry method is used to override the null key and value
private void replaceStaleEntry(ThreadLocal<? > key, Object value,int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (inti = prevIndex(staleSlot, len); (e = tab[i]) ! =null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (inti = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if(slotToExpunge ! = staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }Copy the code
Other issues
3.1 Memory Leakage
Memory leak: Simply put, the space opened is not released after the completion of use, resulting in memory has been occupied.
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
Threadlocal stores its weakly referenced instance object as a key in the ThreadLocalMap. This causes the key to be reclaimed in the absence of strong external references, whereas if the thread that created the Threadlocal keeps running, The value in the Entry object may cause a memory leak (which can never be collected).
- Solutions:
When using Threadlocal, use the remove() method at the end of the code.
3.2 a weak reference
Weak references: Non-required memory. When the JVM does garbage collection, objects associated with weak references are reclaimed regardless of whether memory is sufficient. In Java, using Java. Lang. Ref. The WeakReference class
In fact, the key is designed to be a weak reference: also to prevent memory leaks.