ThreadLocal - JiyangM/spring GitHub Wiki

ThreadLocal是在JDK包里面提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地拷贝,多个线程操作这个变量的时候,实际是操作的自己本地内存里面的变量,从而避免了线程安全问题,创建一个ThreadLocal变量后每个线程会拷贝一个变量到自己本地内存.

源码实现,当set值的时候通过Thread.currentThread() 得到当前的线程,然后通过一个getMap(t) 的方法 获取和当前线程相关的TheadLocalMap

设置value时,要根据当前线程t获取一个ThreadLocalMap类型的map,真正的value保存在这个map中。这验证了之前的一部分想法——ThreadLocal变量保存在一个“线程相关”的map中。

直接看set方法:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); // 使用this引用作为key,既做到了变量相关,又满足key不可变的要求。
        else
            createMap(t, value);
    }

希望进一步深入,可以继续查看ThreadLocal.ThreadLocalMap类的源码:

    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k); // 使用了WeakReference中的key
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

ThreadLocal 导致的内存泄露问题

https://blog.csdn.net/xiaolyuh123/article/details/79869659 https://juejin.im/post/59db31c16fb9a00a4843dc36


使用:

小程序案例,用户请求安全认证,拦截器继承HandlerInterceptorAdapter,preHandle中做自动登录,并把登录后的信息手机号、openid存在ThreadLocal,提供get方法从ThreadLocal中取值,提供 remove方法清除当前线程的threadLoacl变量,防止内存 泄露。get方法的主要调用时机是在controller中可以直接获取手机号码

public class AuthHandlerInterceptor extends HandlerInterceptorAdapter {
    private AuthHandler authHandler;

    public AuthHandlerInterceptor(AuthHandler authHandler) {
        this.authHandler = authHandler;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (!(handler instanceof HandlerMethod)) {
            return true;
        }


        HandlerMethod handlerMethod = (HandlerMethod) handler;
        AssertAuth assertAuth = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), AssertAuth.class);
        if (assertAuth == null) {
            return true;
        }

        boolean autoLogin = authHandler.autoLogin(request);
        if (!autoLogin) {
            throw new InvalidAuthException();
        }

        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        SecurityContextHolder.clearContext();
    }
}

public class SecurityContextHolder {
    private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>();

    public static void setContext(SecurityContext context) {
        Verify.notNull(context, "Only non-null SecurityContext instances are permitted");
        contextHolder.set(context);
    }

    public static SecurityContext getContext() {
        return contextHolder.get();
    }

    public static void clearContext() {
        contextHolder.remove();
    }
}

谨慎使用-内存泄露

如果任务对象结束而线程实例仍然存在(常见于线程池的使用中,需要复用线程实例),那么仍然会发生内存泄露。

当一个线程调用ThreadLocal的set方法设置变量时候,当前线程的ThreadLocalMap里面就会存放一个记录,这个记录的key为ThreadLocal的引用,value则为设置的值。如果当前线程一直存在而没有调用ThreadLocal的remove方法,并且这时候其它地方还是有对ThreadLocal的引用,则当前线程的ThreadLocalMap变量里面会存在ThreadLocal变量的引用和value对象的引用是不会被释放的,这就会造成内存泄露的。但是考虑如果这个ThreadLocal变量没有了其他强依赖,而当前线程还存在的情况下,由于线程的ThreadLocalMap里面的key是弱依赖,则当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会被在gc的时候回收,但是对应value还是会造成内存泄露,这时候ThreadLocalMap里面就会存在key为null但是value不为null的entry项。其实在ThreadLocal的set和get和remove方法里面有一些时机是会对这些key为null的entry进行清理的,但是这些清理不是必须发生的,下面简单说下ThreadLocalMap的remove方法的清理