This is the 15th day of my participation in Gwen Challenge
I am Chen PI, an ITer of Internet Coding. I search “Chen PI’s JavaLib” on wechat and read the latest articles as soon as possible, reply to [information], and then I can get the technical materials, electronic books, interview materials of first-line big factories and excellent resume templates carefully arranged by me.
ThreadLocal profile
The Threadlocal class provides thread-local variable functionality. This means that data can be stored within a specified thread, and which thread stores data that only the thread itself has access to.
The underlying principle is to maintain a Map variable inside the thread, with the Threadlocal object as the key and the data to be stored as the value. The Threadlocal class acts as an entry point for setting and accessing thread-local variables.
Threadlocal objects are generally defined as private static objects, and thread-local variables are set and obtained through their GET and set methods.
private static final ThreadLocal<UserContext> THREAD_LOCAL = new ThreadLocal<>();
Copy the code
How to use ThreadLocal
ThreadLocal uses methods simply, providing three exposed methods for external calls.
- Void set(T value) : sets thread-local variables
- T get() : Gets thread-local variables
- Void remove() : removes thread-local variables
package com.chenpi;
/ * * *@Description
* @AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
public class ThreadLocalTest {
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
public static void main(String[] args) {
// Set thread-local variables
THREAD_LOCAL.set("I am Chenpi, personal public account [JavaLib of Chenpi]");
// Use thread-local variables
peelChenpi();
// Remove thread-local variables
THREAD_LOCAL.remove();
// Use thread-local variables
peelChenpi();
}
public static void peelChenpi(a) { System.out.println(THREAD_LOCAL.get()); }}// Output the resultI am Chenpi, a personal public accountnull
Copy the code
ThreadLocal source code analysis
The underlying principle of ThreadLocal is to maintain a Map variable inside a thread, with the ThreadLocal object as the key and the data to be stored as the value. The Threadlocal class acts as an entry point for setting and accessing thread-local variables.
The Thread class defines a variable called threadLocals of type ThreadLocalMap, which is a static internal class maintained by ThreadLocal. Each Thread has its own function called threadLocals.
ThreadLocal.ThreadLocalMap threadLocals = null;
Copy the code
Thread locals variables are accessible by default and can only be accessed by classes in the same package. Therefore, Thread locals variables cannot be directly used, which is why different threads can only obtain their own data, achieving Thread isolation. The Threadlocal class is the entry point to access it.
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
Copy the code
The static inner class ThreadLocalMap maintains an Entry array table.
static class ThreadLocalMap {
// Map Entry object, weak reference type, key is ThreadLocal object, value is thread-local variable
static class Entry extends WeakReference<ThreadLocal<? >>{ Object value; Entry(ThreadLocal<? > k, Object v) {super(k); value = v; }}// Initialize capacity 16, which must be a power of 2
private static final int INITIAL_CAPACITY = 16;
// An expanded array to store data. The length must be a power of 2
private Entry[] table;
// Size of the table array
private int size = 0;
// The table array threshold will be expanded if it reaches the threshold
private int threshold; // Default to 0
}
Copy the code
Why does the ThreadLocalMap internal storage mechanism maintain an array? This is because a thread can set multiple thread-local variables through multiple ThreadLocal objects, which are stored in the same ThreadLocalMap object of its own thread. Different ThreadLocal objects can fetch different local variable values for the current thread.
package com.chenpi;
/ * * *@Description
* @AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
public class ThreadLocalTest {
private static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();
private static final ThreadLocal<String> THREAD_LOCAL01 = new ThreadLocal<>();
public static void main(String[] args) {
THREAD_LOCAL.set("I am tangerine peel");
System.out.println(THREAD_LOCAL.get());
THREAD_LOCAL01.set("Tangerine peel is me."); System.out.println(THREAD_LOCAL01.get()); }}Copy the code
How to determine the subscript of the array table of the ThreadLocalMap object of the same thread and how to resolve the array subscript conflict? In fact, it is different from the underlying array + linked list + red-black tree storage structure of HashMap, it only has an Entry array.
ThreadLocal has a static initial hash value, nextHashCode, and each new ThreadLocal object increments this hash value to 0x61C88647.
// Each new ThreadLocal object increments its hash value
private final int threadLocalHashCode = nextHashCode();
// Initial hash value, static variable
private static AtomicInteger nextHashCode =
new AtomicInteger();
/ / since the increment
private static final int HASH_INCREMENT = 0x61c88647;
// Increment once
private static int nextHashCode(a) {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Copy the code
Then calculate the table array subscript is determined by the following algorithm, if the subscript conflicts, then the subscript will continue to judge one bit later, until there is no conflict.
When ThreadLocalMap is first created, the subscript of the first element is computed
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// Subscript calculation of subsequent elements
int i = key.threadLocalHashCode & (len-1);
// The method to calculate the next subscript when subscripts conflict
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
Copy the code
The ThreadLocal set method is a simple entry method for setting thread-local variables.
- First get the ThreadLocalMap variable for the current thread
- If the ThreadLocalMap variable exists, the ThreadLocal object and T data are stored in the ThreadLocalMap variable as key-value pairs
- If the ThreadLocalMap variable does not exist, a new ThreadLocalMap variable is created and bound to the current thread, and the ThreadLocal object and T data are stored in the ThreadLocalMap variable as key-value pairs
// Set thread-local variables
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 get method of the ThreadLocal class, which is an entry method to access thread-local variables, is also very simple.
- First get the ThreadLocalMap variable for the current thread
- If the ThreadLocalMap variable exists, the ThreadLocal object is used as the key and the corresponding thread-local variable is looked up in the ThreadLocalMap variable
- If the ThreadLocalMap variable does not exist, a new ThreadLocalMap variable is created and bound to the current thread, and the ThreadLocal object and NULL are stored in the ThreadLocalMap variable as key-value pairs
// Access thread-local variables
public T get(a) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
returnresult; }}return setInitialValue();
}
private T setInitialValue(a) {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue(a) {
return null;
}
Copy the code
The remove method of the ThreadLocal class directly removes any Entry object whose key is the current ThreadLocalMap object in the thread.
public void remove(a) {
ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
Copy the code
If you notice that all methods in a ThreadLocal class are unlocked, since ThreadLocal ultimately operates on the current thread’s ThreadLocalMap object, there are no thread-safety issues since threads handle their own local variables.
Note that the same ThreadLocal variable that is set in the parent thread is fetched in the child thread. That is, there is no inheritance. The InheritableThreadLocal class is the InheritableThreadLocal class, which I’ll cover in the next article.
ThreadLocal application
ThreadLocal has thread-isolated, thread-safe effects and is a good choice if the data is thread-scoped and different threads have different data.
For example, for a service that requires a user to log in, for each request, we might need to verify that the user is logged in, and that after logging in, the user information will be used in subsequent requests. Then we could put the verified user information into a thread-local variable.
Firstly, define a user information class to store the user information verified by user login.
package com.chenpi;
import lombok.Data;
/ * * *@Description
* @AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
@Data
public class UserContext {
private String userId;
private String userName;
}
Copy the code
Define a management tool class that holds user information. The main user manages user information for the current thread.
package com.chenpi;
/ * * *@Description
* @AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
public class UserContextHolder {
private static final ThreadLocal<UserContext> THREAD_LOCAL = new ThreadLocal<>();
private UserContextHolder(a) {}
public static void setUserContext(UserContext userContext) {
THREAD_LOCAL.set(userContext);
}
public static UserContext getUserContext(a) {
return THREAD_LOCAL.get();
}
public static void removeUserContext(a) { THREAD_LOCAL.remove(); }}Copy the code
Intercepts interfaces that require user permissions, and stores user information inside the current thread. Note that after the request is completed, the user information needs to be cleared to avoid memory leaks.
package com.chenpi;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
/ * * *@DescriptionUser permission authentication interception *@AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
@Component
public class UserPermissionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
// Get the user permission verification annotation
UserAuthenticate userAuthenticate =
handlerMethod.getMethod().getAnnotation(UserAuthenticate.class);
if (null == userAuthenticate) {
userAuthenticate = handlerMethod.getMethod().getDeclaringClass()
.getAnnotation(UserAuthenticate.class);
}
if(userAuthenticate ! =null && userAuthenticate.permission()) {
// Verify user information
UserContext userContext = userContextManager.getUserContext(request);
// Store user information inside the threadUserContextHolder.setUserContext(userContext); }}return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
Object handler, @Nullable Exception ex) {
// After the request is complete, clear the user information of the current thread to avoid memory leakage and user information confusionUserContextHolder.removeUserContext(); }}Copy the code
At this point, we can get the current user information anytime, anywhere through the utility class, without passing the user information through the method parameter display in the same thread as the current request.
And you can see that if the method call chain a-B-C, AB doesn’t need user information, and C does, then you need to pass user information through the method parameters. With ThreadLocal, you don’t have to pass user information through method parameters, so dependency pollution is avoided and the code is cleaner.
package com.chenpi;
import org.springframework.stereotype.Service;
/ * * *@Description
* @AuthorDried tangerine or orange peel *@Date 2021/6/27
* @Version1.0 * /
@Service
public class UserService {
public void chenPiDeJavaLib(a) { UserContext userContext = UserContextHolder.getUserContext(); }}Copy the code