ThreadLocal 你听说过么
也许很多人都听说过,但是估计也有很多人从来没有使用过,其实从听说了解到可以自如的使用之间还有一段不短的路要走,不过有些比较聪明的人可能就很容易就上手,但是像我这样的就需要不断的揣摩和实践才能应用自如。这篇文章就是说给这些资质比较平庸但是通过努力让自己不平庸的人。
什么时候使用呢
说到这里就不得不提多线程编程,因为TreadLocal正如其名是给线程使用,我们说说创建线程的一百种方法吧。
- new Thread
- implements Runable
- implements Callable
- 还有比较特殊的ThreadPoolExecutor
当你在代码中发现这些关键字的时候,你就要注意了,可能一不小心就看到了ThreadLocal而且声明他的方式一般为public static ThreadLocal xxx = new ThreadLocal<>(); 这里是否有个疑问,为啥要用static来修饰呢,说好的是给线程私有使用的,怎么还用static修饰怎么感觉有点怪怪的,先来解答这个问题,我们知道一般我们在应用中用static修饰的都是一些通用的方法,举个例子StringUtils.isEmpty
public static boolean isEmpty(@Nullable Object str) {
return (str == null || "".equals(str));
}
我们都知道StringUtils是工具类,那么好吧,ThreadLocal也是工具类,他不存储变量只是变量的搬运工。为什么这么说呢,我们都知道使用ThreadLocal 存储变量的时候用的是xxx.set(T) 方法,现在就通过这个方法来解答这个问题。
public void set(T value) {
Thread t = Thread.currentThread(); //(1)
ThreadLocalMap map = getMap(t); //(2)
if (map != null) {
map.set(this, value); //(3)
} else {
createMap(t, value);
}
}
// (2)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
先看注释//(1) 在ThreadLocal类中的set方法中首先先拿到当前线程,说白了是谁(线程)调用的xxx.set(T)方法,谁调用的我就找谁,这里就比如说是threadOne吧,谁让他手欠呢,他不调用是不是我也不用费劲写这么一大段,你看着也挺闹心的,为啥要找他呢,这就要看//(2)了,getMap,啥意思,我要获取那个map呢,通过代码我们知道,是t.threadLocals,说明在Thread类中有这么个变量,而且可以通过ThreadLocal的方法给“.” 出来,说明这个变量肯定没有在Thread类中被定义为private,否则除了Thread内部,谁也别想把他“.”出来。这就可以看出来,虽然现在说的是ThreadLocal中的set方法,可他不担责任,首先把 Thread t = Thread.currentThread(); 给拿出来,然后获取t.threadLocals,整个跟ThreadLocal没关系了,有事都是Thread t 的,然后看//(3) map.set(this,value),好这里有个疑问,这个this是谁啊,是线程t么,显然不是,如果是他又何必通过Thread.currentThread();来获取线程呢,那是谁呢,其实是public static ThreadLocal xxx = new ThreadLocal<>(); 是 xxx 啊,因为是我调用xxx.set的,才发生的这一切,没毛病吧,翻译一下其实就是map.set(“xxx”,T); 那么如果再定义一个public static ThreadLocal yyy = new ThreadLocal<>(); 那就应该是map.set(“yyy”,T);这些都存在了Thread类的map里了,可以结合Thread的源码看下。说了这么多我觉得我说明白了,但是你很有可能没看懂,那么我建议多看几遍,给点耐心。
通过上面的描述,我们基本知道了存储的方式和结构,那么使用起来就会轻松多了,但又不得不说到另一个问题,既然是存储变量的作用,那么到底存储在jvm内存模型的哪个位置呢,ThreadLocal存储在堆里,他只是工具类,存哪里说实话不占地方,可是定义在Thread中的map,是Thread类的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。map.set(this,value); 还有一个value存储在哪,如果这个value是一个对象,那么他在堆里,那这里就有一个问题,如果再堆里那ThreadLocal 的set方法,set(this,value)这个value是啥,那必然是对象在堆里的内存地址,引用传递呗,那就要注意一个问题了,就是如果是堆里的同一个对象,那么即便不同的线程来反复操作他,那么其实还是操作的是同一个对象吧,所以这个对象的创建就有说法了,一定要在线程内部创建,说白了,一个线程就要有一个value对象实例,这样就不用担心上面说到的问题了。
还有就是使用ThreadLocal的内存泄漏问题
我们使用一种技术是为了使编码简洁,但是有一个前提,就是不能发生内存泄漏,这个可是个大事,那么为啥会内存泄漏呢,这个跟Thread中的ThreadLocalMap 存储结构有关,说来话长,大家只要记得在线程执行完之前执行一下ThreadLocal的remove方法就好了.
public static ThreadLocal<T> xxx = new ThreadLocal<>();
xxx.remove();
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) {
m.remove(this);
}
}