再议ThreadLocal

为什么要使用ThreadLocal

简单理解,是为了减少参数的传递:有些参数,是同一个线程内多个类的多个方法都要使用的,一种办法是直接在各方法之间传递这些参数,另一种就是使用ThreadLocal。
例如,用户登录后,这个用户信息(用session代表)可能在很多个地方、多个方法中调用。一种办法是直接把用户信息作为参数在方法之间传递,但使用ThreadLocal更简洁。
这篇文章举的例子:
不使用ThreadLocal前,传递session:

      SessionHandler handler = new SessionHandler();
      Session session = handler.createSession();
      handler.getStatus(session);
      handler.getUser(session);
      handler.setStatus(session, "close");
      handler.getStatus(session);

使用ThreadLocal之后:

      SessionHandler handler = new SessionHandler();
      handler.getStatus();
      handler.getUser();
      handler.setStatus("close");
      handler.getStatus();

但如果让我选,我可能还是会选择第一种,直接传递参数的方式,它虽然笨,但不容易出错(更坏的情况是,有可能接手你代码的哥们并不了解ThreadLocal......)。ThreadLocal还可能带来内存泄露的问题,例子在这里

使用ThreadLocal的例子还有:
多数据源切换
Spring事务中的ThreadLocal
使用ThreadLocal存储用户登录信息
使用ThreadLocal实现用户信息随用随取

ThreadLocal的实现原理

详细见这篇文章
核心的原理就是,每个线程都有一个map:

private static final ThreadLocal myThreadLocal = new ThreadLocal();
myThreadLocal.set(myValue);

上面的代码,我们拿到的有3个对象:
Thread.currentThread
myThreadLocal
myValue
拿到了Thread.currentThread,就拿到了它的map,然后往这个map里放,key就是myThreadLocal ,value就是myValue。
那为什么要用map呢?
那是因为,一个线程可能有多个ThreadLocal(定义多个类,在多个类中定义static final的ThreadLocal实例),也就是你可以定义多个ThreadLocal,然后都绑定到同一个线程上。

ThreadLocal的几个重要的认识

1.ThreadLocal并不是解决变量共享的问题

因为它根本就没有共享变量,每个线程是有自己的变量。
例如下面的
hibernate session管理
每次调用sessionFactory.openSession都会创建一个新的session(而不是copy,后面会说到这个copy的问题),然后绑定在调用的线程上,后续同一个线程,使用的就是同一个session。Spring的事务也是基于类似的原理。
源码(注意sessionFactory.openSession()):

public class DAO {

    private static final ThreadLocal session = new ThreadLocal();
    private static final SEssionFactory sessionFactory = 
            new Configuration().configure().buildSessionFactory();

    public static Session getSession() {
        Session session = (Session) DAO.session.get();
        if (session == null) {
            session = sessionFactory.openSession();
            DAO.session.set(session);
        }
        return session;
    }

    public static void close() {
        getSession().close();
        DAO.session.set(null);
    }

}

但是, ThreadLocal实例是共享的,多个线程之间,ThreadLocal是同一个

什么意思呢?

private static final ThreadLocal myThreadLocal= new ThreadLocal();

上面代码的myThreadLocal是static final,只创建一次,它在不同的线程中,是作为key的,所以它在多个线程中是同一个。那它会有线程不安全的问题吗?没有。因为它只是作为getValue的key,只读不写。而且要注意的是,myThreadLocal本身并不存储value,value是存储在线程的map字段的。这一点非常重要,要看源码才能明白。

2.ThreadLocal没有copy变量

官方文档的copy我理解是一个虚指,不是实指。个人推测,它说的copy可能是指,代码里是只定义了一个session变量,但实际上每个线程都会有一个session,所以看起来是把session“复制”(copy)了一份。事实上,每个session都是不同的对象,都是sessionFactory新创建的。Spring管理数据库connection的例子中,每次取connection然后调用ThreadLocal.set(connection)时,也是从数据库连接池中取的不同的connection。

This class provides thread-local variables.These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容