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

  1. Data isolation between threads
  2. Performs transaction operations and stores thread transaction information
  3. Database connection and Session management
  4. Break the constraints between layers when passing objects across layers
  5. .

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.

  1. In the source code for the Thread class, there is threadLocals, which is ThreadLocalMap
  2. The key in the Entry of a ThreadLocalMap is ThreadLocal, and the value is our own
  3. ThreadLocal is a weak reference, and when null, it is garbage collected by the JVM
  4. 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.