大家好,JDK1.8推出的ThreadLocal是经常被提问到的一个知识点,也是在项目中常用到的技术,所以在面试的时候经常被面试管问,请你说说自己对ThreadLocal的理解,提问的方式有很多,可能是由浅入深,因此只要我们理解透彻了这个技术,那么不管怎么问都能游刃有余。
笔者从以下几个方面来解析和理解ThreadLocal:
- ThreadLocal是什么?
- ThreadLocal如何使用?
- ThreadLocal源码解析。
- ThreadLocal内存泄漏问题的解析。
————————————————————————我是分割线———————————————————————————
ThreadLocal是什么?
根据名称可以看出 ThreadLocal被称为线程变量,意思是 ThreadLocal中填充的变量属于当前线程,而当前线程隔离了其他线程。在每个线程中, ThreadLocal为变量创建一个副本,然后每个线程可以访问自己内部的副本变量。
ThreadLocal如何使用?
我们可以将它作用与存储用户的信息。
private static ThreadLocal<MiaoShaUser> userHolder = new ThreadLocal<MiaoShaUser>();
/**
* 通过方法向ThreadLocal中赋值
* @param user
*/
public static void setUser(MiaoShaUser user) {
userHolder.set(user);
}
public static MiaoShaUser getUser() {
return userHolder.get();
}
创建一个ThreadLocal,写两个方法,一个set一个get,获取到用户数据的时候set以下,这样就可以在需要的时候去调用当前线程的用户数据,并且不会出现线程不安全的问题,但它还要一个严重的问题内存溢出,我们一会再说。
上面的使用我们只用到了两个方法,set()和get(),但其实它还有其他的方法。
//初始化
protected T initialValue();
public T get();
public void set();
//删除
public void remove();
光从单词就能看出是非常好理解。
ThreadLocal源码解析。
首先我们从最重点的set()方法来进行解析
/**
* 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 t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
在 set方法中,我们可以看到,首先获取当前线程 t,然后调用 getMap获取 ThreadLocalMap,如果有 map,就把当前线程对象 t作为 key,把要存储的对象作为值存在 map中。当 Map不存在时对其进行初始化。
ThreadLocalMap是什么?先来看看源码。
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.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
//下面省略......
源码需要静心的去看,如果实在不想看就直接看我的总结吧==
实际上, ThreadLocalMap是 ThreadLocal的一个静态内部类,其中定义了一个 Entry来保存数据,还包括继承的弱引用。将 ThreadLocal用作 Entry内部的key,将我们设置的 value用作value。
还有一个getMap();
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
当前线程 t被调用,它在当前线程 t中返回成员变量 threadLocals。事实上threadLocals就是 ThreadLocalMap。
现在咱们简单的总结以下:
ThreadLocal本身是不存储值的,而是靠它的ThreadLocalMap,而ThreadLocalMap底层是靠的Entry存储数据,所以我们可以简单的来理解,
将ThreadLocal比作一条裤子,ThreadLocalMap比作一个兜,兜在裤子上,数据是放在兜里面的。要去获取数据就必须要通过这条裤子,兜的底层是Entry通过k-v的形式存储数据,k就是当前的裤子,v是你自己存储的数据(比如说钞票==,对象)。
ThreadLocal内存泄漏问题的解析。
前面我们说到它虽然线程安全,但是它存在一个问题那就是内存泄漏。
首先我们要明白为什么会内存泄漏,前面也说了ThreaLocal是一个弱引用,什么是弱引用就是当它为null时候,就会被垃圾回收机制给带走,重点就是,如果我们的ThreadLocal突然为null,然后就被回收了,但此时我们的ThreadLocalMap它的生命周期是和Thread相同的,简单理解就是,裤子没了,兜还在,兜里面还有我们的数据,这就造成了内存泄漏。
如何解决那:我们必须在使用完ThreadLocal后,执行remove()方法,避免内存溢出。