ThreadLocal 是什么?
顾名思义,ThreadLocal 可以为每个线程独立存储不同的值(线程本地变量)。其意义在于高并发场景下变量被多个线程访问互不影响,有效避免线程安全问题和同步带来的性能开销。当然它也存在一定缺陷,由于每个线程都会创建 ThreadLocal 变量,就会带来一定的内存消耗;它的思想就是“以空间换时间”。
特殊情况:InhertiableThreadLocal 并不是只存储当前线程的值,它默认会集成父类中的值。
ThreadLocal 类方法定义
public class ThreadLocal<T> {
public T get();
private T setInitialValue();
public void set(T value);
public void remove();
}
- get:用于获取当前线程私有的 ThreadLocal 变量;
- setInitialValue:可以进行重写设置当前 ThreadLocal 对应的数据,用于第一次调用 get 时懒加载获取;
- set:设置当前 Thread 的 ThreadLocal 值;
- remove:删除 ThreadLocal 中的数据;
get 方法具体实现
/**
* 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();
//获取Thread类中定义的ThreadLocal.ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//判断map是否为null
if (map != null) {
//获取当前ThreadLocal对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
//获取set的值
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
//当map为null或未设置值时调用
return setInitialValue();
}
其中 ThreadLocalMap 是 ThreadLocal 中的静态内部类,内部维护了一个 Entry 数组的 table,可以看作是一个 kv 的 map,只不过 key 是当前 ThreadLocal 生成的 Hash 值;
static class ThreadLocalMap {
private Entry[] table;
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
}
setInitialValue 具体实现
当 get 方法中 map 为 null 或未设置值时会调用 setInitialValue 方法,接下来看看它的实现:
/**
* 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() {
//value等于重写initialValue后返回的值,否则默认返回null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//map为null直接添加到table中,否则新建map
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
值得注意的是其中 initialValue 默认是返回 null 的,当然你也可以选择重写来懒加载数据,当调用 get 方法的时候才获取,就像这样:
ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
int i = 1;
@Override
protected Integer initialValue() {
return i;
}
};
其中当 map 为 null 是会调用 createMap 方法,将创建的 ThreadLocalMap 赋值到当前 Thread 的 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);
}
public
class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
set 方法具体实现
和上面 setInitialValue 中实现几乎一样,这里就不赘述了...
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
* @param value
* the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
//获取当前Thread
Thread t = Thread.currentThread();
//获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
//map为null直接添加到table中,否则新建map
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
remove 方法具体实现
获取当前线程中的 ThreadLocal.ThreadLocalMap,清除 table 数组中以当前 ThreadLocal Hash 为 key 的数据。
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
* @since 1.5
*/
public void remove() {
//获取当前Thread类中定义的ThreadLocal.ThreadLocalMap变量
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//删除以当前ThreadLocal为key设置的数据
m.remove(this);
}
关于内存泄露的问题
抛出问题
- 首先 ThreadLocal 实例被线程的 ThreadLocalMap 实例持有,也可以看成被线程持有。
- 如果应用使用了线程池,那么之前的线程实例处理完之后出于复用的目的依然存活
所以,ThreadLocal 设定的值被持有,导致内存泄露。
结论
首先 Entry 是继承 WeakReference<ThreadLocal<?>> 的,也就是弱引用,ThreadLocalMap 的 key 使用的是 ThreadLocal 的弱引用,所以并不会导致内存泄露,关于 java 中的四种引用我会在后面的文章中记录。
最后
第一次写源码分析,有不好的地方欢迎指正。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于