preface
What is ThreadLocal?
A review of the source code notes that this class provides thread-local variables. What are thread-local variables? ThreadLocal stores variables that belong to the current thread and are isolated from other threads. ThreadLocal creates independently initialized copies of variables for each thread through its get() or set() methods; ThreadLocal instances are typically private static fields in a class that you want to associate state with a thread (for example, user ID or transaction ID); Each thread holds an implicit reference to a copy of its thread-local variable as long as the thread is active and the ThreadLocal instance is accessible; After a thread disappears, all copies of its thread-local instances are garbage collected (unless there are other references to those copies).
Usage scenarios
- Data isolation between threads
- Performs transaction operations and stores thread transaction information
- Database connection and Session management
- Break the constraints between layers when passing objects across layers
- .
The source code section
ThreadLocal is a generic class that specifies the type to store. The source code provides only one constructor, which is usually used alone or in conjunction with the initialValue() method, overridden to provide an initialValue when ThreadLocal is instantiated. The implementation is shown in the following code:
A constructor
Method 1: Instantiate ThreadLocal and set an initial value
ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue(a) {
return 1; }};ThreadLocal provides a static method withInitial that looks cleaner and more elegant than ThreadLocal
ThreadLocal<Integer> threadId = ThreadLocal.withInitial(() -> 1);
The Supplier interface provides a get method labeled @functionalInterface that supports Lambda expressions
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal SuppliedThreadLocal
static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
private final Supplier<? extends T> supplier;
SuppliedThreadLocal(Supplier<? extends T> supplier) {
this.supplier = Objects.requireNonNull(supplier);
}
@Override
protected T initialValue(a) {
returnsupplier.get(); }}Copy the code
The set () method
Sets the local variable value for the current thread
The set(T value) method sets the specified value. In most cases, subclasses do not need to override this method and can rely on the initialValue() method to set the initialValue of thread-local variables.
// Sets the current thread-local variable
public void set(T value) {
// The current thread instance
Thread t = Thread.currentThread();
// Get the ThreadLocalMap from the current thread
ThreadLocal.ThreadLocalMap map = getMap(t);
if(map ! =null)// Overwrite if it exists
map.set(this, value);
else // Create a ThreadLocalMap and store the value of the current thread t as the Map key into the ThreadLocalMap
createMap(t, value);
}
/** ** get ThreadLocalMap ** from the current thread@paramT Current thread *@returnThe map stores a copy of the current thread-local variable */
ThreadLocal.ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/** * Creates a corresponding ThreadLocalMap for the thread and stores the value in it. * The newly created ThreadLocalMap is assigned to the threadLocals variable in the thread. * This also indicates that the corresponding data is stored in each thread, so the operation of each thread does not affect the data of other threads * *@paramT Current thread *@paramFirstValue Initial value in the map */
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
ThreadLocalMap is a custom hash map that is only suitable for maintaining current thread local values. Interested students can look at the source code is how to store the current thread local variables
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
The get () method
/** * Returns the current thread local value (the value of the local variable), or the value returned by calling the initialValue() method ** if the current thread has no local value@returnThe local value of the current thread */
public T get(a) {
//t: current thread
Thread t = Thread.currentThread();
//map: a copy of the current thread local variable
ThreadLocal.ThreadLocalMap map = getMap(t);
if(map ! =null) {
//this: represents the current ThreadLocal object and gets the corresponding value of ThreadLocal
ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
if(e ! =null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;// The corresponding value of ThreadLocal
returnresult; }}return setInitialValue();// Call the setInitialValue method to return the initial value
}
/** * another implementation of set(), used to establish initial values. If the user overrides the set() method, use it instead of set(). * *@returnReturn the initial value */
private T setInitialValue(a) {
T value = initialValue();// if you override the initialValue() method, you can also get the initialValue here
Thread t = Thread.currentThread();// The current thread
ThreadLocal.ThreadLocalMap map = getMap(t);
if(map ! =null)
map.set(this, value);
else
createMap(t, value);
return value;
}
Copy the code
Emove () method
If the thread-local variable is then read by the current thread get, its value will be reinitialized by calling its initialValue() method, unless its value was set by the current thread, This may cause the initialValue() method to be called multiple times in the current thread.
public void remove(a) {
ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());
if(m ! =null)
m.remove(this);
}
private void remove(ThreadLocal
key) {
ThreadLocal.ThreadLocalMap.Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for(ThreadLocal.ThreadLocalMap.Entry e = tab[i]; e ! =null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return; }}}//clear() GC special processing
private T referent; /* Treated specially by GC */
public void clear(a) {
this.referent = null;
}
Copy the code
In actual combat
The business scenario
Suppose your project has a UserController class that calls the userService.getUserInfo(Integer UID) method, which is used to get user information based on the user ID, and the business requirements change. You need to query the user information based on the string token passed in by the front end, but you find that this method has been called in multiple places in your project, if you change the input structure of the method or rewrite the query based on the token, it will cause the whole body to be pulled. Is there a way to reduce the scope affected and satisfy both UID and token queries?
ThreadLocal is a ThreadLocal that allows users to transfer tokens across different layers of data. The data isolation level is threadlevel
Code implementation
The get method in UserController is used to simulate querying user information
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class UserController { @Autowired private IUserService userService; @GetMapping(path = "/user/get", produces = MediaType.APPLICATION_JSON_UTF8_VALUE) public UserInfoDTO get(Integer uid) { return userService.getUserInfo(uid); }}Copy the code
UserServiceImpl implements the IUserService interface to initialize test data, and getUserInfo implements obtaining user information based on UID and Token
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@Slf4j
@Service
public class UserServiceImpl implements IUserService {
static ThreadLocal<String> tokenStore = new ThreadLocal<>();
/** * temporary data that simulates data stored in a database table */
private List<UserInfoDTO> data = new ArrayList<>();
public UserServiceImpl(a) {
initData();
}
@Override
public UserInfoDTO getUserInfo(Integer uid) {
if (null! = uid) {// Simulate querying user information by Id
Optional<UserInfoDTO> optionalByUid = data.stream().filter(u -> u.getUid().equals(uid)).findFirst();
if (optionalByUid.isPresent()) {
log.info("User ID {} found information", uid);
returnoptionalByUid.get(); }}// get the user's token from ThreadLocal
String token = tokenStore.get();
log.info(UserServiceImpl thread: {} obtained token: {}, Thread.currentThread().getName(), token);
if (null! = token) {// Simulate querying user information based on token
Optional<UserInfoDTO> optionalByToken = data.stream().filter(u -> u.getToken().equals(token)).findFirst();
if (optionalByToken.isPresent()) {
returnoptionalByToken.get(); }}// User information does not exist
return null;
}
/** * Temporary data */
private void initData(a) {
this.data = new ArrayList<>();
this.data.add(new UserInfoDTO(1."zhangsan"."Zhang"."4b86736c73674b7c910657a9c6786470"));
this.data.add(new UserInfoDTO(2."lisi"."里斯"."4b86736c73674b7c910657a9c6786471"));
this.data.add(new UserInfoDTO(3."jack"."Jack"."4b86736c73674b7c910657a9c6786473"));
log.info("Data initialization completed"); }}Copy the code
UserFilter the UserFilter is used to obtain the token in the Header. In the UserFilter, the token is placed in the (ThreadLocal) tokenStore defined by UserServiceImpl to pass objects across the hierarchy
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@Slf4j
@WebFilter
@Component
public class UserFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {}@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
log.info("Enter UserFilter...");
// Get the token passed from the front end
String token = httpServletRequest.getHeader("Token");
if (StringUtils.isNotBlank(token)) {
// Store it in ThreadLocal
UserServiceImpl.tokenStore.set(token);
log.info(UserFilter thread: {} stores token: {}, Thread.currentThread().getName(), token);
}
chain.doFilter(request, response);
}
@Override
public void destroy(a) {}}Copy the code
Code the measured
Query user information based on the user UID
To query user information based on tokens, you can see from the console print that the use of ThreadLocal completes the cross-layer transfer of tokens without affecting the method’s original external input structure
conclusion
ThreadLocal isn’t that complicated to use, but using ThreadLocal properly in multithreaded programming can make your work more efficient and your code more elegant. The following diagram illustrates the relationship between ThreadLocal, Thread, and ThreadLocalMap.
- In the source code for the Thread class, there is threadLocals, which is ThreadLocalMap
- The key in the Entry of a ThreadLocalMap is ThreadLocal, and the value is our own
- ThreadLocal is a weak reference, and when null, it is garbage collected by the JVM
- If a ThreadLocalMap is null, the garbage collector will collect the value of a ThreadLocalMap. If a ThreadLocalMap is null, the garbage collector will collect the value of a ThreadLocalMap. This creates a memory leak.
Solution: After using ThreadLocal, run remove to avoid memory overflow.
Problems discussed
What other problems will ThreadLocal encounter in practice?
If there is any mistake in the above views, welcome comments to point out.