前言
本篇,我们来介绍一下ThreadLocal的作用和原理,基于JDK1.8
使用场景
场景1
每个线程需要一个独享的对象,比如工具类Random、SimpleDateFormat
场景2
每个线程内需要保存类似于全局变量信息(如在拦截器中获取用户信息,并在该线程内各方法执行保持不变),不被其他线程共享,避免参数传递麻烦
使用ThreadLocal的优点
- 达到线程安全
- 不需要加锁,提高执行效率
- 更高效的利用内存、节省开销
- 免去传参的繁琐
源码、原理分析
知道ThreadLocal的使用场景和优点后,我们来看下它具体是怎么实现的
首先要搞清楚Thread、ThreadLocal、ThreadLocalMap三者间关系,如下图所示
image.png
以下是源码实现,实现也并不复杂,我把重要实现代码捡出来了
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
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();
}
ThreadLocalMap getMap(Thread t) {
return t.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;
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
}
注意点
内存泄漏
Thread-->ThreadLocalMap-->Entry(key 为null)-->Value
概念:某个对象不再有用,但是占有的内存却不能被回收
看上面的源码实现,ThreadLocalMap中Entry继承WeakReference,是弱引用,若这个对象只被弱引用关联,那么这个对象就可以被GC回收,但是这个key对应value是个强引用链路,导致value无法回收,就可能出现OOM
如何避免内存泄漏(阿里规约)
调用Remove方法,删除Entry对象,可以避免内存泄露
优先使用框架中维护的
比如RequestContextHolder、DateTimeContextHolder
每次Http请求都对应一个线程,线程间相互隔离,这也是ThreadLocal的典型应用场景