ThreadLocal
ThreadLocal是什么
threadlocal而是一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据,官方解释如下。大致意思就是ThreadLocal提供了线程内存储变量的能力,这些变量不同之处在于每一个线程读取的变量是对应的互相独立的。通过get和set方法就可以得到当前线程对应的值。
做个不恰当的比喻,从表面上看ThreadLocal相当于维护了一个map,key就是当前的线程,value就是需要存储的对象。
ThreadLocal特性
ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
- Synchronized是通过线程等待,牺牲时间来解决访问冲突
- ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。
正因为ThreadLocal的线程隔离特性,使他的应用场景相对来说更为特殊一些。比如在hibernate中session就存在与ThreadLocal中,避免synchronized的使用。当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal。
ThreadLocal源码阅读
set()方法
public void set(T value) {
// 首先获得当前线程
Thread t = Thread.currentThread();
// 通过当前线程t,中获得一个ThreadLocalMap,这就好玩儿了
// 这里的getMap()方法实际返回的就是t本身包含的一个ThreadLocalMap
ThreaaLocalMap map = getMap(t);
if (map != null)
// 如果map不为空,就把this作为key,value作为值
// this是谁?就是ThreadLocal这个当前对象
map.set(this, value);
else
creatMap(t, value);
}
// getMap()方法实际返回的就是t本身包含的一个ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
// 这个threaLocals 就是 t 本身包含的一个 ThreadLocalMap成员变量
return t.threadLocals;
}
这里的 ThreadLocalMap 是 ThreadLocal 的静态内部类。但是Thread这个类的内部居然有一个ThreadLocalMap 的成员变量。
所以ThreadLocal对象的set()方法最终set到哪儿去呢,是设置到到当前线程Thread.currentThread()的 threadLocals(ThreadLocalMap的实例)里。
所以下面这个程序是否可以理解?
public class ThreadLocal2 {
// 试着体会【volatile的线程可见性】和【ThreadLocal的线程隔离性之间】的区别
//volatile static Person p = new Person();
static ThreadLocal<Person> tl = new ThreadLocal<>();
public static void main(String[] args) {
Thread t1 = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t2 用 tl get一个Person()对象
System.out.println(tl.get());
});
Thread t2 = new Thread(()->{
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// t2 用 tl set了一个Person()对象
tl.set(new Person());
});
t2.start();
t1.start();
}
static class Person {
String name = "zhangsan";
}
}
- 问:为什么t2通过tl设置的person,t1获取不到?
线程有一个threadlocals的成员变量,他是ThreadLocalMap类型的,也就是说每个线程自己维护着一个ThreadLocalMap,而ThreadLocal的get()/set()操作,是从线程自己的成员变量中去找去设置,所以这就是ThreadLocal线程隔离性的原理。
就好比你拿着你的卡取ATM机上存了100,然后我拿着我的卡去ATM机上取这100,肯定是取不到的,因为你存在了你自己的卡里而不是我的卡。(你我好比是线程,ATM机好比是ThreadLocal,你我的卡好比是ThreadLocalMap,100好比是要存取的变量)
volatile的线程可见性 vs ThreadLocal的线程隔离性之间
ThreadLocal的作用
- Spring 的声明式事务管理,是怎么保证每个事务拥有一个单独的connection连接的?就是把connection放到了事务所在线程的ThreadLocalMap里边,与Thread进行绑定,事务提交或者回滚后,解除绑定。(在事务管理中,一个线程创建一个事务,每个事务的上下文都应该是独立拥有数据库的connection连接的,否则在数据提交或回滚过程中就会产生冲突)。
弱引用在ThreadLocal中的体现
public class T03_WeakReference {
public static void main(String[] args) {
WeakReference<M> m = new WeakReference<>(new M());
System.out.println(m.get());
System.gc();
System.out.println(m.get());
ThreadLocal<M> tl = new ThreadLocal<>();// 创建一个ThreadLocal对象
tl.set(new M());// 赋值一个M对象给到tl
tl.remove();
}
}
问题一
问题一:ThreadLocalMap里面的Entry的key为什么是所引用
ThreadLocalMap 类
priavte void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLcoalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLcal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStateEntry(key, value, i);
returnl
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
里面的Entry它继承自WeakReference。
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
// k 也就是 ThreadLocal 对象,是由父类中的弱引用变量来维持。
// 所以 k 在外部强引用释放后,就等着被gc释放,是否后 k = null
super(k);
// v 是由 Entry 自己的成员变量,作为一个强引用来维持。
value = v;
}
}
所以 tl 是个强引用指向ThreadLocal对象,而map里的key通过Entry里的一个弱引用指向ThreadLocal对象。tl是个局部变量,方法一结束,它就消失了,当它消失了之后,如果还有一个强引用指向ThreadLocal对象,它能被回收吗?肯定不行。而且由于很多线程是长期存在的,有些服务器线程365天不间断运行,那么map会长期存在,那么key就会长期存在,那么ThreadLocal对象永远不会消失,这样就导致了内存泄漏(memory leak)。所以如果是弱引用,就不会导致这个问题了。
- 内存泄漏(memory leak)是指一块内存长期漏在那儿,不被回收。
- 内存溢出(OOM)是指内存爆了。内存泄漏不一定导致OOM,只要内存够大
问题二
问题二:ThreadLocal不手工remove会导致内存泄漏?为什么?
如果key对应的ThreadLocal被释放了,那么key为空了,key对应的value就永远访问不到了。如果这种情况越来越多,这时候还是会有内存泄漏(value访问不到但也没被回收)
所以,必须的,如果ThreadLocal不用了,必须remove掉,要不然还是会引起内存泄漏。(为什么呢?因为Map还存在,Entry还存在,只是Entry的key为null了,这个Entry访问不到了而已,只要Entry存在,value就存在。ThreadLocal的坑就在这里) 。如下:
ThreadLocal<M> tl = new ThreadLocal<>();
tl.set(new M());
// tl不用了,务必要回收,不然会引起内存泄漏
tl.remove();
- 养成好习惯,ThreadLocal没用就remove掉。