前言
共享变量一直是并发中的老大难问题,每个线程都对它有操作权,所以线程之间的同步很关键,锁也就应运而生。这里换一个思路,是否可以把共享变量私有化?即每个线程都拥有一份共享变量的本地副本,每个线程对应一个副本,同时对共享变量的操作也改为对属于自己的副本的操作,这样每个线程处理自己的本地变量,形成数据隔离。事实上这就是ThreadLocal了。
就线程同步而言,锁可以认为是时间换空间,ThreadLocal可以认为是空间换时间,其实个人觉得这么描述有点强行往同步靠的意思,专业的人干专业的事,同步这个还是就老老实实交给锁吧。 ThreadLocal最适合的是变量在线程间隔离而在方法或类间共享的场景。
ThreadLocal的应用场景
ThreadLocal在Spring中事务的应用
我们可能每天都在使用Spring写dao写service,真的是一个非常爽的框架.我们能用这么爽是因为Spring在底层把脏活儿累活儿全干了。在一个Service中我们可能要写很多个dao,如果多个dao都用不同的JDBC连接,很费时费力费资源不说,事务性就得不到保证了,因为我们知道事务需要在一个连接内才能得以实现。事务对应着连接,所以如果我们每个线程对应一个连接,也就能保证我们在一个service中很爽的叨叨叨了,Spring底层正是使用ThreadLocal对连接进行了封装,可劲儿叨吧你就
ThreadLocal在日志中的应用
slf4j中有一个类叫MDC,全名叫Mapped Diagnositc Context,这个类基于 InheritableThreadLocal ,先不细说,暂且把它当成ThreadLocal。我们可以给每个服务或用户行为编号,每次客户调用服务就把编号写到threadlocal包含的变量中,最后打印出来。似乎有点dubbo链路监控的意思(然而我dubbo才学会拼写没多久,没看过源码就不妄议了hhh)
看完应用场景似乎对Threadlocal已经有所了解,不过,本着知其然并知其所以然的心态,还是去学习下源码怎么实现的吧
源码分析
ThreadLocalMap
先去Thread类中瞄一眼,Thread中有2个成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
两个变量都是ThreadLocalMap类,咦?说了半天 ThreadLocal怎么又冒出来个ThreadLocalMap
别急,是这样子的,默认这两个变量都是null,正如我们现在看到的。
一旦我们调用ThreadLocal的set或者get才会真正创建他们。也就是你以为你把变量交给ThreadLocal了,其实这小子转手就给ThreadLocalMap了,ThreadLocal就是套在 ThreadLocalMap外面的一层壳而已。
ThreadLocal的组成如下
可以看出,就跟map基本一样,key是ThreadLocal的引用,value则是由开发者设置,即本地变量
ThreadLocal
set
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
// 这里this是 ThreadLocal的实例引用
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- 首先获取当前线程
- 以当前线程为key去查找当前线程的map即threadLocals变量
- 如果threadLocals不为空,就把ThreadLocal引用作为key,value传给map
- 否则创建map,也就是初始化当前线程的threadLocals变量
再次注意值存放的实际位置是Thread中的ThreadLocalMap变量,ThreadLocalMap是一个map,key是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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
其实就很简单了,set的反过程嘛,先得到当前线程,然后得到成员变量threadLocals,如果threadLocals不为空,返回本地变量对应值,否则初始化threadLocals
初始化threadLocals
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
判断当前threadLocals是否为空,如果不为空,设置当前ThreadLocal的实例引用对应变量为null,否则调用createMap创建threadLocals变量
remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如果threadLocals变量不为空,删掉map中当前ThreadLocal对应实例引用的本地变量。
ThreadLocal的内存溢出问题
从上边一路走下来我们应该了解了,每一个线程中都有一个ThreadLocalMap
类型的threadLocals变量,这个map中key为ThreadLocal的实例引用,value为对应的本地变量。如果这个线程不消亡,开发者也没有采用remove操作及时清除掉不再使用的变量,这些变量就会一直存在map中,直到撑爆你的内存,造成内存溢出问题
ThreadLocal的继承问题
假设一个场景,我们需要异步起一个线程发送邮件给用户,子线程需要打日志,那么就需要父线程中本地变量,这里子线程可以通过父线程的ThreadLocal得到父线程设置的的本地变量吗?答案是不可以,ThreadLocal不支持父子线程间的继承传递
这里可以使用InheritableThreadLocal,没错就是上边日志那块我们提到过的。它是ThreadLocal的子类,其实用法跟ThreadLocal完全一样,不过,它可以使得子线程访问在父线程中设置的变量,简单看下这一实现的原理
首先看下线程的构造方法,说实话之前真没看过这个构造方法,我觉得应该很多人都没看过吧
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
这个init方法追进去,你会看到底下这行代码,init方法体太长,我就只截取出来这最关键的一部分
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
parent就是父线程,如果父线程中的inheritableThreadLocals 不为空,就执行 createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
这个代码就不继续展开了,过程就是把父线程中的 inheritableThreadLocals 的值全部复制一份到子线程的 inheritableThreadLocals 中
总结
- 每个线程对应一个ThreadLocalMap,ThreadLocal其实就是套在ThreadLocalMap上的一层壳
- ThreadLocalMap的key是ThreadLocal的实例引用,value是我们像设置的本地变量
- 若是线程一直不停,threadLocalMap中的本地变量就会越来越多,注意即使remove掉不再使用的变量,防止内存溢出
觉得还可以的话,关注一哈我呀哈哈哈
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。