Local-global and ThreadLocal variables
We often use local and global variables.
Local variable declarations in a method or code block are reclaimed at the end of the code block for no reference, as shown below
Local variables are thread exclusive, and the life cycle exists only in the method block.
Global variables include static variables and object member variables and are accessible to all threads, as shown below:
But threads reading and writing the same global variable need to solve the concurrency problem.
Another type of variable is called the ThreadLocal variable. Why threadLocal? Some threads need to store information about the current thread, such as request information, user information, session information, etc. This variable has its own value for each thread; it is not local to the code block, nor is it global. ThreadLocal variables are thread-private and use between threads does not affect each other. This is called a ThreadLocal, which stores the scope of a variable appropriately between global and local variables. The diagram below:
Threadlocal can:
- Don’t worry about concurrency security
- Access global variables as if they were local variables
So why does this work? This article will get to the bottom of it by implementing a Threadlocal yourself.
Demo-threadlocal basic working mode
Here is a typical scenario for a ThreadLocal application:
The code is as follows:
package com.example.demo;
public class ThreadLocalTest {
public static void main(String[] args) {
for (int i = 1; i < 4; i++) {
Handler handler = new Handler("user"+ i); handler.start(); }}}class Request{
String user;
public void setUser(String user) {
this.user = user;
}
public Request(String user) {
this.user = user; }}class Handler extends Thread{
String user;
public Handler(String user) {
this.user = user;
}
ThreadLocal<Request> request =new ThreadLocal() ;
@Override
public void run(a) {
request.set(new Request(user));
Random random = new Random(400);
try {
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+ request.get().user); }}Copy the code
The output
Thread-1: user2
Thread-0: user1
Thread-2: user3
Copy the code
All three threads use the same variable ThreadLocal Request, and the values set and fetched are corresponding, using global variables as if they were local variables. Individual threads use Request as if they were using local variables of individual threads, with no thread-safety issues.
This is because the variable is stored in a member variable of the thread object. Each thread has a variable of type ThreadLocalMap that holds the ThreadLocal variable and its corresponding set value (wrapped as an Entry object).
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
WeakReference<ThreadLocal<? The key representing the Entry is a weak reference. The key weak reference type is a ThreadLocal Object, and the value can be any Object.
Implement Threadlocal yourself
The demo looks simple, and it’s not hard to jump in and see the source code for Threadlocal. I’m going to start with the weak reference principle and experiment until I get my hands dirty and implement a Threadlocal.
Start with key recycling ♻️
Why start with recycling weak reference keys? Because this is one of the most tongue-in-cheek aspects of the ThreadLocal principle, I’ll explain why later, and you can follow me step by step from here.
Weak reference GC collection
WeakReference objects are freed whenever full GC occurs.
@Test
void test(a){
WeakReference<byte[]> weakReference = new WeakReference<byte[] > (new byte[1]);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
Assertions.assertTrue(weakReference.get()==null);
}
[B@1786f9d5
null
Copy the code
So after GC occurs, the array object New Byte [1] that was allocated on the heap is reclaimed.
However, if there is a strong reference to the object, it will not be released, as shown in the following demo
@Test
void test(a){
byte[] bytes = new byte[1];
WeakReference<byte[]> weakReference = new WeakReference<byte[]>(bytes);
System.out.println(weakReference.get());
System.gc();
System.out.println(weakReference.get());
Assertions.assertTrue(weakReference.get()==null);
}
[B@1786f9d5
[B@1786f9d5
org.opentest4j.AssertionFailedError:
Expected :true
Actual :false
Copy the code
Here, GC roots has two variables, bytes and weakReference, as follows:
GC differences between the two scenarios:
- In the above scenario, there are only weakReference variables, and weak references can be cleaned
- The following scenario also has bytes variables that are strong references and cannot be cleaned up
Start: Inherit the Entry of WeakReference
After understanding WeakReference, you can start to work, as follows:
public class ReferenceTest {
@Test
void test(a){
Entry[] table=new Entry[16];
for (int i = 0; i < 3; i++) {
table[i]=new Entry(new byte[1]."hello"+i);
}
System.gc();// These entries are also cleared during gc
for (int i = 0; i < 3; i++) {
System.out.println("entry key = "+ table[i].get()); }}}class Entry extends WeakReference<byte[] >{
Object value;
public Entry(byte[] referent, Object value) {
super(referent);
this.value = value;
}
}
entry = null
entry = null
entry = null
Copy the code
- Replace the byte array object above with Entry
- Entry inherits WeakReference< Byte []> and has a member value. Entry is an array object type that mimics ThreadLocalMap.
- In the same way, these entries are also cleared during GC
One step further: weak reference Threadlocal
WeakReference
public class ReferenceTest {
@Test
void test(a){
Entry[] table=new Entry[16];
for (int i = 0; i < 3; i++) {
Request request = new Request("user" + i);
ThreadLocal<Request> threadLocal = new ThreadLocal<>();
table[i]=new Entry(threadLocal,request);// call threadlocal.set
}
for (int i = 0; i < 3; i++) {
System.out.println("entry key = " + table[i].get());
System.out.println("value = " + table[i].value);
}
System.gc();// During GC, such WeakReference variables will be cleaned.
for (int i = 0; i < 3; i++) {
System.out.println("entry key = " + table[i].get());
System.out.println("value = "+ table[i].value); }}}class Entry extends WeakReference<ThreadLocal<? >>{
Object value;
public Entry(ThreadLocal
referent, Object value) {
super(referent);
this.value = value; }}Copy the code
The results of
entry key = java.lang.ThreadLocal@1786f9d5
value = com.example.demo.Request@704d6e83
entry key = java.lang.ThreadLocal@43a0cee9
value = com.example.demo.Request@eb21112
entry key = java.lang.ThreadLocal@2eda0940
value = com.example.demo.Request@3578436e
entry key = null
value = com.example.demo.Request@704d6e83
entry key = null
value = com.example.demo.Request@eb21112
entry key = null
value = com.example.demo.Request@3578436e
Copy the code
It’s only a few lines of code, but you’ve almost simulated the implementation of a ThreadLocal and ThreadLocalMap.
Yes! ThreadLocal is really that simple.
Continue: key and subscript conversion
There is a problem with the above implementation, where is the key put in, and which index?
hashacode
If each ThreadLocal has a unique HashCode, it can be searched by key and subscript when setting up.
ThreadLocal evaluates HashCode using static methods.
private static AtomicInteger nextHashCode =
new AtomicInteger();
/** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */
private static final int HASH_INCREMENT = 0x61c88647;
/** * Returns the next hash code. */
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code
Not only is the hashacode of each ThreadLocal object free from collisions, but it is also guaranteed to minimize collisions with subscript key.threadLocalHashCode & (len-1).
Array hash table ThreadlocalMap
Add a hashcode implementation to MyThreadLocal, and ThreadLocalMap implements the specific get and set methods as follows:
class ThreadMock{
MyThreadLocal.ThreadLocalMap threadLocals;
public ThreadMock(MyThreadLocal.ThreadLocalMap threadLocals) {
this.threadLocals = threadLocals; }}class MyThreadLocal<T> extends ThreadLocal{
ThreadMock threadMock;
public MyThreadLocal(ThreadMock threadMock) {
this.threadMock= threadMock;
}
static class ThreadLocalMap{
public ThreadLocalMap(a) {}int len=16;
Entry[] table=new Entry[len];
Object get(MyThreadLocal
key){
int i = key.threadLocalHashCode & (len-1);
if(table[i]! =null) {if (table[i].get()==key) {
return table[i].value;
}else {
// May be replaced by another, resulting in}}return null;
}
public void set(MyThreadLocal
key,Object value){
int i = key.threadLocalHashCode & (len-1);
table[i]=newEntry(key,value); }}public Object get(a){
return threadMock.threadLocals.get(this);
}
public void set(Object value){
threadMock.threadLocals.set(this,value);
}
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode =
new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode(a) {
returnnextHashCode.getAndAdd(HASH_INCREMENT); }}class Entry extends WeakReference<MyThreadLocal<? >>{
Object value;
public Entry(MyThreadLocal
referent, Object value) {
super(referent);
this.value = value; }}Copy the code
Testing:
@Test
void test(){
ThreadMock threadMock= new ThreadMock(new MyThreadLocal.ThreadLocalMap());
for (int i = 0; i < 5; i++) {
Request request = new Request("user" + i);
MyThreadLocal<Request> threadLocal = new MyThreadLocal<>(threadMock);
threadLocal.set(request);
}
System.out.println("threadMock = " + threadMock);
}
Copy the code
- The subscripts generated in ThreadLocalMap are 0,7,14,5,12
The disadvantage is that there are still conflicts. If you try several times, the same key will appear: 0,7,14,5,12, 3, 10,1, 8, 15, 6, 13, 4, 11, 2 9 **0** 7. On the 17th time, the subscript is 0, and it starts to collide.
So there are conflicts to resolve.
There are several ways to resolve hashMap conflicts, using the zip method in ThreadLocal.
Zip method to resolve hash table conflicts
The get rewritten:
Object get(MyThreadLocal
key){
int i = key.threadLocalHashCode & (len-1);
Entry entry = table[i];
if(entry ! =null && entry.get()==key){
return entry.value;
}
return getEntryAfterMiss(key,i,entry);
}
private Entry getEntryAfterMiss(ThreadLocal<? > key,int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while(e ! =null) { ThreadLocal<? > k = e.get();if (k == key)
return e;
if (k == null)
// The garbage is collected. Let's clean up k, and then some values
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
Copy the code
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
/ / clean up the value
tab[staleSlot].value = null;
// Clear this entry
tab[staleSlot] = null;
size--;
Entry e;
int i;
for(i = nextIndex(staleSlot, len); (e = tab[i]) ! =null; i = nextIndex(i, len)) { ThreadLocal<? > k = e.get();if (k == null) {// The following entry key is also gc
e.value = null;
tab[i] = null;
size--;
} else {/ / entry key exists
int h = k.threadLocalHashCode & (len - 1);
if(h ! = i) {// Not exactly in slot I
tab[i] = null;
while(tab[h] ! =null)// Find a new position nearest to hh = nextIndex(h, len); tab[h] = e; }}}return i;
}
Copy the code
Set also does some conflict handling and null cleanup.
capacity
If the amount of data stored exceeds a threshold, consider expanding the hash table.
Summary of implementation
Implementing a ThreadLocal is actually quite simple because it is a ThreadLocal variable, and there is no need to worry about concurrent locking for get, set, and expansion operations. However, implementing a fully fledged ThreadLocal is not always easy, considering hash table conflicts and capacity expansion.
In the implementation process, although we understand the role of weak references here, there is still a problem that is not clear, that is, we introduced the implementation from weak references but did not explain, the next chapter will clarify this problem.
Possible problems
Because the key is never recycled as long as the hash table is not recycled, weak references are used to point to the key to prevent memory leaks.
But why doesn’t Value do that? The ThreadLocal memory leak problem is caused by value.
Why does value not use weak references
Why not use weak references for values? For example:
@Test
void test(a){
ThreadMock threadMock= new ThreadMock(new MyThreadLocal.ThreadLocalMap());
for (int i = 0; i < 5; i++) {
MyThreadLocal<Request> threadLocal = new MyThreadLocal<>(threadMock);
threadLocal.set(new WeakReference<>(new Request("user" + i)));
}
System.out.println("threadMock = " + threadMock);
System.gc();
System.out.println("threadMock = " + threadMock);
}
Copy the code
-
Threadlocal. set(new WeakReference<>(new Request(“user” + I))); Value is reclaimed after gc.
-
Gc reclaims value, resulting in a manslaughter.
-
The reason value is not set so well is because there is probably no other reference, so the common use of value is to not use WeakReference.
Value’s possible memory leak problem is nothing compared to random reclamation (losing data).
This will cause the value’s memory to remain in the heap forever (if not cleaned up by get and set) and never be reclaimed, causing a memory leak, so developing a good remove habit will help.
Reclaim value: remove function
To prevent memory release problems caused by a value, you can call the remove function to manually release the value.
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
Copy the code
In essence:
if(table[i].get()==null){
table[i].value=null;
}
Copy the code
Why use weak references for key
It’s a bit of a roundabout, but why does key still insist on using weak references? This is because of usage. The classic use of a ThreadLocal variable is to declare a class variable or static variable pointing to the object as mentioned in the demo above,
In the demo, because the Handler class member references this ThreadLocal, even if a GC occurs in the middle, it will not be released by the GC. Gc occurs only when the Handler object is released or when static classes unload, which is often expected.
The problem summary
The principles and usage of key and value are summarized as follows: