ThreadContext and ThreadLocal of shiro

problem

Today, I looked at the previous project written with shiro framework and looked at the source code carefully. Suddenly, I was curious. We usually use securityutils getSubject(); Method to obtain the of the current user. This class is a self-contained class under shiro, that is, shiro has encapsulated the logged in object to the inside after we log in. So I thought, if different users log in through the login interface at the same time, how does shiro judge which user this session is? Moreover, I can see that it does not generate an ioc bean to be managed by spring. It can be said that shiro has its own security manager, and all activities are mainly handled by its security manager. Then, how does it handle different threads? Let's look at its getsubject () method:

    public static Subject getSubject() {
        Subject subject = ThreadContext.getSubject();
        if (subject == null) {
            subject = (new Subject.Builder()).buildSubject();
            ThreadContext.bind(subject);
        }
        return subject;
    }

You can see that there is a new class ThreadContext, and the user information is obtained from the getSubject method in this class

ThreadContext

Look at ThreadContext:
ThreadContext provides a method to bind and unbind objects to the current thread based on key value pairs. The internal HashMap is used to maintain the key value pairs of each thread. If the desired behavior is to ensure that bound data is not shared between threads in a pooled or reusable threading environment, the application (or more likely the framework) must bind and delete any necessary values at the beginning and end of stack execution, respectively (i.e., either explicitly or entirely through the clear method).

This note makes it clear that it is an internal hashmap maintenance key value pair. Let's see what kind of hashmap it is:

public abstract class ThreadContext {


    public static final String SECURITY_MANAGER_KEY = ThreadContext.class.getName() + "_SECURITY_MANAGER_KEY";
    public static final String SUBJECT_KEY = ThreadContext.class.getName() + "_SUBJECT_KEY";
//The point is here
    private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();

Variables of this static final type:
private static final ThreadLocal<Map<Object, Object>> resources = new InheritableThreadLocalMap<Map<Object, Object>>();
The attribute modified by static final indicates that once a value is given, it cannot be modified and can be accessed through the class name. There is a ThreadLocal inside to separate threads from each other. ThreadLocal is called thread local variable, that is, ThreadLocal creates a copy of the variable in each thread, and each thread can access its own internal copy variable.

The above resources reference points to an InheritableThreadLocalMap class. Look at the source code:

  private static final class InheritableThreadLocalMap<T extends Map<Object, Object>> extends InheritableThreadLocal<Map<Object, Object>> {

    
        @SuppressWarnings({"unchecked"})
        protected Map<Object, Object> childValue(Map<Object, Object> parentValue) {
            if (parentValue != null) {
                return (Map<Object, Object>) ((HashMap<Object, Object>) parentValue).clone();
            } else {
                return null;
            }
        }
    }
}

It seems nothing special, just a copy of the parameter is returned
Let's look at InheritableThreadLocal:
Here, a ThreadLocal is customized to create a new map, return the value of the parent thread and obtain the current map

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    protected T childValue(T parentValue) {
        return parentValue;
    }
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    } 
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

After reading InheritableThreadLocal, we will return to the ThreadContext just mentioned. We know that it is internally referenced by a ThreadLocalMap. Here are the key methods in ThreadContext:

    public static Map<Object, Object> getResources() {
        if (resources.get() == null){
            return Collections.emptyMap();
        } else {
            return new HashMap<Object, Object>(resources.get());
        }
    }

The value in the current map reference is obtained in the getResources method above. In fact, resources The get () method is to call the get method of ThreadLocal to get the value put into the current variable:

//get method of ThreadLocal
    public T get() {
        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;
                return result;
            }
        }
        return setInitialValue();
    }

Let's look at the put method:

//put method of ThreadContext
 public static void put(Object key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        if (value == null) {
            remove(key);
            return;
        }

        ensureResourcesInitialized();
        resources.get().put(key, value);

        if (log.isTraceEnabled()) {
            String msg = "Bound value of type [" + value.getClass().getName() + "] for key [" +
                    key + "] to thread " + "[" + Thread.currentThread().getName() + "]";
            log.trace(msg);
        }

The put method is called by the following bind method:

  public static void bind(SecurityManager securityManager) {
        if (securityManager != null) {
            put(SECURITY_MANAGER_KEY, securityManager);
        }
    }

It's very clear that it actually puts a threadcontext class. getName() + “_SECURITY_MANAGER_KEY”; Is key, and the securityManager of the current thread is Value into the map. securityManager is an interface with login and createSubject methods. When shiro logs in, he will finally go to the login method in the implementation class of securityManager, and the user will finally put it into the current thread through threadlocalmap.
So let's learn ThreadLocal again!

ThreadLocal

ThreadLocal: its function is to share a value in the current thread, which can be session connection, etc. this value is different in each thread

So why is it decorated with Static?

From the description in the official Java document: the ThreadLocal class is used to provide local variables inside the thread. When this variable is accessed in a multithreaded environment (through get and set methods), it can ensure that the variables of each thread are relatively independent of those in other threads. ThreadLocal instances are usually of type private static and are used to associate threads and thread contexts.
The principle of ThreadLocal is that there is a collection object of ThreadLocalMap inside the Thread. Its key is ThreadLocal, and value is the copy of the variable you want to store,
Different threads are separated by their ThreadLocalMap. If the variable ThreadLocal is non static, it will cause different ThreadLocal objects to be generated every time an instance is generated. Although there will be no exceptions in the program, it will waste memory resources Cause a memory leak

ThreadLocal use case
You can store JDBC connections through ThreadLocal, which has achieved the effect of controlling things
For example, when you modify a database operation, you need to record the log. At this time, it is possible for a single thread to run the code. When two or more threads operate on the database, it is possible that the previous thread has closed the connection, and the connection has been closed when the subsequent thread uses the connection.

solve the problem
Each thread corresponds to a database connection. You don't care about me and I don't care about yours. Finally, the above problems will not be encountered by the unified submission of things, so you can use the ThreadLocal object to solve this problem

Keywords: Java Spring intellij-idea

Added by robtbs on Sat, 19 Feb 2022 20:02:46 +0200