The problem

For Web projects, each time a user logs in, Session information is stored in Redis, and the token is returned to the client as a token for the user’s Session. After that, all requests go through an interceptor. This interceptor takes Session information from Redis and puts it into ThreadLocal based on the token. When there are too many concurrent requests, Session information may be incorrect.

After analysis, the error flow is as follows: User 1 logs in to the system and accesses the system using thread A and saves user 1’s Session information to thread A’s TheadLocal. Then, A user 2 who has not logged in also accesses the system, and the server assigns thread A to process this request again. At this time, user 2 gets user 1’s Session information.

ThreadLocal

TheadLocal is a handy tool for in-thread variable sharing provided by the JDK. During Web development, the server side can use TheadLocal to store Request and Response. Pass through the method call stack without showing it. It’s easy to get instance variables we’ve stored anywhere in the thread call stack.

class WebContext {
    private TheadLocal<HttpServletRequest> request = new TheadLocal<>();
    private TheadLocal<HttpServletResponse> response = new TheadLocal<>();
    public static void setRequest(HttpServletRequest req) {
        request.set(req);
    }
    public static HttpServletRequest getRequest(a) {
        request.get();
    }
    public static void setResponse(HttpServletResponse resp) {
        response.set(resp);
    }
    public static HttpServletResponse getResponse(a) { response.get(); }}class AbcController {
    public Object userInfo(a) {
        HttpServletRequest request = WebContext.getRequest();
        // request.getParameter()
        // ...}}Copy the code

Take a look at TheadLocal’s code:

// TheadLocal two important methods
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map ! =null)
        map.set(this, value);
    else
        createMap(t, value);
}
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();
}
Copy the code

T = thread.currentThread (); Gets the current thread instance. ThreadLocalMap map = getMap(t); This is why fetching thread variables from the same thread yields the same instance, and since each thread instance stores its own thread variables, these variables are thread-safe.

public class Thread implements Runnable {
    /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
Copy the code

This is the threadLocals property declared in the Thread class. As you can see the actual storage thread variable is ThreadLocal ThreadLocalMap class, the class and Map similar storage Key/Value pair (Key, Value), one of the Key is the thread instance, the Value is the thread variables.

Tomcat thread pool

Since the operating system is very resource-intensive to create threads, many applications use thread pools. System initialization is to create a certain amount of threads at once, and these threads will be reused and shared. That is to say, threads will not be destroyed after being used until the application is stopped.

As a result, different users request to reuse the same thread. As a result, different users obtain the same Session information, resulting in Session information confusion.

To solve

Set up an interceptor that sets Session information to local thread variables at the front end of the request and clears Session information at the back end.

// The code is simplified
public class SessionInspector extends HandlerInterceptorAdapter {
    @Override
    public boolean preHandle(...). throws Exception {
        // ThreadLocal.set(Session)
        return true;
    }
    
    public void afterCompletion(...). {
        // ThreadLocal.clear()}}Copy the code

conclusion

ThreadLocal gives us a convenient way to share variables within threads, providing a way to keep data thread-safe. However, consider the case of thread reuse in multi-threaded environment. If the information stored in the ThreadLocal is the session and other user-related information, the information in the ThreadLocal should be cleaned up at the end of the thread task.