0 快速理解
ThreadLocal的实现机制不复杂,它将自身实例作为key,和需要保存的value版本值,一起存入到当前线程所持有的一个map当中,代码可以简单的描写为(当然实际的代码并不是这样):
`Thread.currentThread().threadLocals.put(this, value);// threadLocal.set(T)
Thread.currentThread().threadLocals.get(this);// threadLocal.get()`
而每个线程都有自己的一个独立的map,这样就实现了每个线程独立地改变自己的副本,不会和其它线程冲突。
1 简介
ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。
线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。
2 ThreadLocal的应用
- 在Spring中应用
Spring使用ThreadLocal解决线程安全问题。一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理,让它们也成为线程安全的状态,这样有状态的Bean就可以在多线程中共享了。
- 在Slf4j 日志输出中的应用
Java Web项目中,通常使用实现了 Slf4j 的Logback或Log4j来进行日志输出,Slf4j 中定义了 MDC 接口,要求实现多线程间日志隔离,Logback 和 Log4j 正是利用ThreadLocal来实现的。更多内容将会在另一篇专门介绍MDC的文章中讲解。
3 实现原理
前面提到:
ThreadLocal将自身实例作为key,和需要保存的value(具体大变量副本值)一起存入到当前线程的一个map当中。
这段描述涉及三个类:Thread、ThreadLocal、ThreadLocalMap,它们实际的关系如下:
- ThreadLocal中声明了一个静态内部类ThreadLocalMap。可以将它视为一个map容器,key是ThreadLocal类型,value是Object。
- ThreadLocal类中定义了:
//静态内部类
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
...
}
- Thread类中拥有一个ThreadLocalMap类型的成员变量。这个成员变量ThreadLocalMap便是此线程用来存储各个ThreadLocal变量在本线程中具体副本值的。
- Thread类:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
//成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
下面来看一下ThreadLocal工作原理。
1) 给某个ThreadLocal变量, 设置一个与当前线程所绑定的具体值。
-> 使用ThreadLocal的set()方法:
/**
* Sets the current thread's copy of this thread-local variable to the specified 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);
}
}
其中:
(1)Thread.currentThread()是一个native方法,可以拿到当前的线程t。
(2)通过getMap(Thread t)方法获取当前线程所持有的ThreadLocalMap;
(3)然后将变量的副本值设置到这个ThreadLocalMap对象中(key=this,即当前ThreadLocal实例,value=传入的副本值value)。
(4)当然如果(2)中获取到的ThreadLocalMap对象为空,就通过createMap方法给线程t创建一个新ThreadLocalMap,并将key-value存入。
/**
* Get t
* he map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
2) 获取某个ThreadLocal变量, 与当前线程所绑定的具体副本值。
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
其中:
(1)Thread.currentThread()是一个native方法,可以拿到当前的线程t。
(2)通过getMap(Thread t)方法获取当前线程所持有的ThreadLocalMap;
(3)如果ThreadLocalMap不为空,则读取map中的value;
(4)如果ThreadLocalMap为空,则创建一个map,并返回初始化的值。
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
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;
}
从这里可以看出线程隔离的秘密就在于:。
每个线程中都有一个独立的ThreadLocalMap,存放着它自己所拥有的各个ThreadLocal变量副本值。
在外部代码中对ThreadLocal进行get或set时,能够获取到当前线程(它所在的线程),进而可以获取到当前线程对应的map,所以实际操作的一直是本线程所对应的ThreadLocalMap版本。每个线程的ThreadLocalMap只能自己被读取和修改。
这样在不同线程中对ThreadLocal进行操作时,实际修改和读取的是不同的ThreadLocalMap。从而实现了变量访问在不同线程中的隔离。
4 内存泄漏问题
为什么会有内存泄漏
Thread是通过ThreadlocalMap的key的形式对Threadlocal产生了弱引用。
当threadlocal实例的强引用都被释放以后, threadlocal实例本身可能会在gc时被清除。由于key不存在了,所以map里面对应的value永远不会被访问到了。然而value却没有被回收,所以存在着内存泄露。
WeakReference是Java语言规范中为了区别直接的对象引用而定义的另外一种引用关系。它的标志性的特点是:reference实例不会影响到被引用对象的GC回收行为(可以理解为弱引用,不在GC前对对象被引用情况的统计范围内),只不过在被对象回收之后,reference实例想获得被应用的对象时程序会返回null。
扩展一下,Java中HashMap类型中对键是强引用。同时java也提供WeakHashMap,它对键是是弱引用。但是在WeakHashMap中,当key可以被GC时,整个Entry都会被清除。
为什么使用的是弱引用
当threadLocal实例可以被GC回收时,系统可以检测到该threadLocal对应的Entry是否已经过期来自动做一些清除工作,否则如果不清除的话容易产生内存无法释放的问题。
如果对threadLocal不是弱引用,即使除了作为threadLocalMap的value对象即使在其他位置不再使用,也会被所引用导致无法被GC回收。
避免内存泄漏的最佳实践
1)ThreadLocalMap会在set,get以及remove等方法中会对key已经被回收的entry做自动删除。
(set以及get不保证所有过期slots会在操作中会被删除,而resize则会删除threadLocalMap中所有的过期slots)
例如:在ThreadLocal的get方法中,使用了map.getEntry()方法,getEntry()中会对查找不到的key进行清除。
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);//此处着手清除key为null的Entry
}
2)最安全的办法是调用ThreadLocal的remove方法,把当前ThreadLocal从当前线程的ThreadLocalMap中移除(包括key,value)。来彻底避免可能的内存泄露。
每次使用完ThreadLocal,都调用它的remove()方法,清除无用数据。
5 总结
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。
ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,但是大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal所保存的变量只能是Object类型,不能使用基本数据类型。