ThreadLocal学习笔记

对ThreadLocal的理解

ThreadLocal主要是用来存储线程内局部变量。每个线程都有自己的局部变量;这种变量在多线程环境下访问时(通过get或set方法访问)时,能保证各个线程里的变量独立于其他线程内变量之外。ThreadLocal实例通常是类中的private static字段,它们希望将状态与某一个线程相关联。

每个线程都保持对其线程局部变量副本的隐式引用,只要线程是活动的并且ThreadLocal实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用).

可以总结为一句话: ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之前一些公共变量的传递的复杂度。

举个例子,我出门需要先坐公交再做地铁,这里的坐公交和坐地铁就好比是同一个线程内的两个函数,我就是一个线程,我要完成这两个函数都需要同一个东西:公交卡(北京公交和地铁都使用公交卡),那么我为了不向这两个函数都传递公交卡这个变量(相当于不是一直带着公交卡上路),我可以这么做:将公交卡事先交给一个机构,当我需要刷卡的时候再向这个机构要公交卡(当然每次拿的都是同一张公交卡)。这样就能达到只要是我(同一个线程)需要公交卡,何时何地都能向这个机构要的目的。

有人要说了:你可以将公交卡设置为全局变量啊,这样不是也能何时何地都能取公交卡吗?但是如果有很多个人(很多个线程)呢?大家可不能都使用同一张公交卡吧(我们假设公交卡是实名认证的),这样不就乱套了嘛。现在明白了吧?这就是ThreadLocal设计的初衷:提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。

ThreadLocal基础API

set方法

set方法用来设置当前线程的ThreadLocal的值。

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

通过上面的代码,我们首先知道ThreadLocal支持泛型,据说旧版本JDK这个API是不支持泛型的。其次,当set方法被调用时,首先获取当前线程的ThreadLocals属性,如果该属性为null,就new一个ThreadLocalMap赋给当前线程的threadLocals属性;注意,当前ThreadLocalMap实例的key是this,也就是当前线程(对象)。

get方法

用来获取与当前线程存储在ThreadLocal实例里的值。

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }
 /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }
   /**
     * Returns the current thread's "initial value" for this
     * thread-local variable.  This method will be invoked the first
     * time a thread accesses the variable with the {@link #get}
     * method, unless the thread previously invoked the {@link #set}
     * method, in which case the {@code initialValue} method will not
     * be invoked for the thread.  Normally, this method is invoked at
     * most once per thread, but it may be invoked again in case of
     * subsequent invocations of {@link #remove} followed by {@link #get}.
     *
     * <p>This implementation simply returns {@code null}; if the
     * programmer desires thread-local variables to have an initial
     * value other than {@code null}, {@code ThreadLocal} must be
     * subclassed, and this method overridden.  Typically, an
     * anonymous inner class will be used.
     *
     * @return the initial value for this thread-local
     */
    protected T initialValue() {
        return null;
    }

通过上面的代码逻辑,我们知道获取当前线程存储在ThreadLocal实例里的值,其实就是获取当前线程threadLocal属性的value值。但是,如果当前线程的threadLocal为null,也就是之前没有进行过set操作。这个时候,最终会调用initValue()方法;这个方法是protected的,所以我们知道该方法是可以被子类重载的。作用也就是提供get时当前线程的threadLocal属性为null时的默认值;通常该方法都会以匿名内部类的形式被重载。比如:

    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        protected Integer initialValue() {
            return 0;
        }
    };

remove方法

remove方法用于将当前线程的ThreadLocal绑定的值删除。在某些情况下,需要手动调用该方法,防止内存泄漏。

总结

每个Thread都拥有自己的ThreadLocalMap实例(该类是ThreadLocal的内部类),这个实例存储了以当前对象(this)为key,value是真正需要存储的对象。

好处:

线程局部变量高效保存:在常见的MVC三层架构中,如果我们想在多层使用同一个变量,我们简单的做法是将该变量作为参数往下传递,但是这样会加大层次之前的耦合力度。或者,我们可以new一个静态全局的大Map;将当前线程作为key,以实际需要存储的值作为value,put到这个大Map中;这样,当线程执行结束的时候,该线程所保存的线程局部量还强引用的保存在这个大Map中,浪费无用内存。ThreadLocal的实现,让线程局部变量交由具体线程自己管理,当进程执行结束的时候,由GC进行回收。

ThreadLocal实际应用场景

最常见的ThreadLocal使用场景为 用来解决数据库连接 Session管理等。

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    public Connection initialValue() {
        return DriverManager.getConnection(DB_URL);
    }
};
 
public static Connection getConnection() {
    return connectionHolder.get();
}

实际项目中的ThreadLocal使用

public class RequestThreadLocal {
	private static final ThreadLocal<HttpServletRequest> threadLocal = new ThreadLocal<HttpServletRequest>();
	private static final ThreadLocal<List<String>> historyThreadLocal = new ThreadLocal<List<String>>();
	private static final Log log = LogFactory.getLog(RequestThreadLocal.class);
	
	public static HttpServletRequest get(){
		return threadLocal.get();
	}
	
	public static void set(HttpServletRequest request){
		log.debug("Put the pagination object into thread local");
		threadLocal.set(request);
	}
	public static void remove(){
		log.debug("clear the pagination object from thread local which related with current thread.");
		threadLocal.set(null);
	}
}

//调用
@Controller
public class ControllerFilter implements Filter{
	@Override
	public void destroy() {}

	@Override
	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
			RequestThreadLocal.set((HttpServletRequest) req);
			chain.doFilter(req, res);
			RequestThreadLocal.remove();
	}

	@Override
	public void init(FilterConfig filterConfig) throws ServletException {}
}

//调用
/**
 * 获取当前登录用户
 * @return
 */
public static AclUserDto getLoginUser(){
	 HttpSession session = RequestThreadLocal.get().getSession();
     return (AclUserDto) session.getAttribute(LoginInfo.SESSIONUSER.getDesc());
}

使用ThreadLocal实现简单的读写分离

@Component
@Aspect
public class DataSourceMethodInterceptor {

    @Before("execution(* com.xxx.xxx.xxx.xxx.service.impl.*.*(..))")
    public void dynamicSetDataSoruce(JoinPoint joinPoint) throws Exception {
        String methodName = joinPoint.getSignature().getName();
        // 查询读从库
        if (methodName.startsWith("select") || methodName.startsWith("load") || methodName.startsWith("get") || methodName.startsWith("count") || methodName.startsWith("is")) {
            DynamicDataSourceHolder.setDataSource("slave");
        } else { // 其他读主库
            DynamicDataSourceHolder.setDataSource("master");
        }
    }

}

总结

  • 每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程私有的值保存到其中,各个线程互不干扰。

  • ThreadLocal的应用场合,我觉得最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

参考文章

posted @ 2017-11-22 20:09  做个有梦想的咸鱼  阅读(946)  评论(0编辑  收藏  举报