再议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.