其实,网上已经有不少关于它的描述了,也都很详细,但也大多也都有模糊的地方,其实这个不能怪作者。毕竟,这jdk是天才的脑子写的,谁看都不理解,但是,我们也只能从源代码中细细看一些端倪,也就能理解jdk作者的想法思路了。
再次申明:如果你对ThreadLocal没有一定的使用经历,就别看我写的了,要不然你会更蒙的。
先上 ThreadLocal.class的两个重要方法,T get()与set(T value)
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
第一句获得当前线程(这个属于废话,不多说)
再看getMap(Thread t)在 ThreadLocal.class中的描述
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
我将方法体内前两步合并 ThreadLocalMap map=Thread.currentThread().threadLocals。
看来我们需要看看Thread.class中对threadLocals的定义了。OK,上源代码:
ThreadLocal.ThreadLocalMap threadLocals = null;
就这样,他居然是 ThreadLocal中的ThreadLocalMap类型(Map类型)。是不是感觉有点坑爹了,变量声明居然放另一个类里。(先来个题外话,所有set方法都是放到ThreadLocalMap中,而threadLocals是Thread的成员变量,所以应该可以理解为什么有人说“ThreadLocal是线程局部变量”了吧。)
咱再看看ThreadLocalMap到底是怎么实现的吧。在jdk中,你会发现,ThreadLocalMap是ThreadLocal静态内部类
static class ThreadLocalMap {
private void set(ThreadLocal key, Object value)
ThreadLocalMap(ThreadLocal firstKey, Object firstValue)
}
我只是给出方法体就行了。现在理一理ThreadLocal的set方法。如果当前线程的ThreadLocalMap=null,则初始化,key=ThreadLocal。但如果ThreadLocalMap不为空,就
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
你看,还是key=ThreadLocal。
要知道不同的ThreadLocal是有区别的
class ThreadLocal {
private final int threadlocalhashcode=nextHashCode();
private static int nexthashcode=0;
private final static int HASH_INCREMENT=0x61c88647;
private static synchronized int nextHashCode() {
int h=nexthashcode;
nexthashcode=h + HASH_INCREMENT;
return h;
}
每一个ThreadLocal会有一个不同的threadlocalhashcode。
再看ThreadLocal的get方法
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
依旧是根据当前线程获取。
所以ThreadLocal的用处就是在每个Thread中定义一个当前线程特有的ThreadLocal的ThreadLocalMap,用这个map存放你想要的东西,也就是所谓的不是多线程共享一个变量,而是每个线程一个变量,互不干扰。
但是hibernate有一段很傻逼的代码让你无力吐槽。其实不是hibernate傻逼,是很多人的文章中引用他却不解释他
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
所以线程共享一个ThreadLocal。真是这样吗?答案肯定否定的。这个要结合hibernate其他的代码看才行,光看这段,没有任何意义的。
总之,ThreadLocal不是用来解决对象共享访问问题的,而主要是提供了保持对象的方法和避免参数传递的方便的对象访问方式。归纳了两点:
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。(回答hibernate的目的)
当我们调用A.threadLocal.set(user) 时,set()做了以下几件事:
1. 得到当前线程Thread 对象
2. 通过当前线程对象得到当前线程的ThreadLocalMap 对象
3. 将ThreadLocal静态实例作为key,user对象作值,存放到ThreadLocalMap 中。
PS:因为ThreadLocal是静态的,所以每个线程中的 ThreadLocalMap的key都是相同的,不同的只是存放的容器ThreadLocalMap。
就如hibernate中的用法一样,key都是同一个,只是这个静态内部类不一样。
引用一个网上很好的例子
ThreadLocal不是用来解决对象共享访问问题的,而是为了处理在多线程环境中,某个方法处理一个业务,需要递归依赖其他方法时,而要在这些方法中共享参数的问题。例如有方法a(),在该方法中调用了方法b(),而在b方法中又调用了方法c(),即a-->b--->c,如果 a,b,c都需要使用用户对象,那么我们常用做法就是a(User user)-->b(User user)---c(User user)。但是如果使用ThreadLocal我们就可以用另外一种方式解决:
1. 在某个接口中定义一个静态的ThreadLocal 对象,例如 public static ThreadLocal threadLocal=new ThreadLocal ();
2. 然后让a,b,c方法所在的类假设是类A,类B,类C都实现1中的接口
3. 在调用a时,使用A.threadLocal.set(user) 把user对象放入ThreadLocal环境
4. 这样我们在方法a,方法b,方法c可以在不用传参数的前提下,在方法体中使用threadLocal.get()方法就可以得到user对象。
上面的类A,类B ,类C就可以分别对应我们做web开发时的 web层的Action--->业务逻辑层的Service-->数据访问层的DAO,当我们要在这三层中共享参数时,那么我们就可以使用 ThreadLocal 了