目录
什么是ThreadLocal?
ThreadLocal,意为线程本地变量
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。
如何使用ThreadLocal?
我们先看下面这段代码
public class ThreadTest{
// 创建ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
static void print(String str) {
// 打印当前线程副本变量的值
System.out.println(str + ":" + localVariable.get());
}
public static void main(String[] args) {
// 创建线程1
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
localVariable.set("this is threadOne local variable");
print("threadOne");
System.out.println("threadOne remove after:" + localVariable.get());
}
});
// 创建线程2
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
localVariable.set("this is threadTwo local variable");
print("threadTwo");
System.out.println("threadTwo remove after:" + localVariable.get());
}
});
// 开启线程
threadOne.start();
threadTwo.start();
}
}
通过运行结果,我们可以看出,两个线程通过调用print方法都打印出了当前线程副本变量的值,然后又在run()方法中获取到了当前线程的副本变量的值。
接下来我们在print()方法中添加一条语句,也就是输出完之后就将副本变量删除掉
// 清除当前线程副本变量
localVariable.remove();
然后再来看一下运行结果
我们可以发现,在run()方法中再次获取localVariable变量的值时,已经获取不到了,因为那个副本变量已经在当前线程中删除掉了。
内存泄漏
为什么会导致内存泄漏?
ThreadLocalMap 使用 ThreadLocal 的弱引用作为key,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 就会被回收,由此一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,也就没有办法再去访问对应的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value 永远无法回收,然后下次再使用的时候则会继续创建新的,越来越多,最终造成内存泄漏。
如何避免内存泄漏?
为了避免内存泄漏,当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量,如果一直不删除,最终会导致内存泄漏。
源码解析:
Thread类中有一个threadLocals和inheritableThreadLocals,它们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个特殊的HashMap
void set()方法
(在当前线程中,设置一个变量的副本)
先获取当前线程,然后根据当前线程作为key,获取到对应线程的变量,如果map不为空就为这个map赋值,否则就创建一个新的变量,并为其赋值
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap()源码如下:
就是获取对应线程自己的变量threadLocals
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
createMap()源码如下:
创建一个新的threadLocals变量
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
T get()方法
(获取当前线程中副本变量的值)
首先获取当前线程,然后根据当前线程作为key,获取到对应线程的ThreadLocals变量,如果变量不为空,则返回当前线程绑定的本地变量,否则执行setInitialValue()
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();
}
getEntry源码如下:
官方解释:此方法本身只处理快速路径::直接命中已存在的键,为了最大限度地提高直接命中的性能而设计的...
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);
}
setInitialValue()源码如下:
先设置一个value,初始化为null,然后获取当前线程,在获取当前线程的threadLocals变量,如果变量不为空,则设置变量的值为value(也就是null),否则就新建一个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;
}
void remove()方法
(删除当前线程中的副本变量)
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
InheritableThreadLocal类
ThreadLocal不支持继承性:也就是说,同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的,那么怎样才能在子线程也能获取到呢?这时就需要使用InheritableThreadLocal类了。
- InheritableThreadLocal继承自ThreadLocal,其提供了一个特性,就是让子线程可以访问在父线程中设置的本地变量。
- 当父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量里面。
例:
public class ThreadTest{
// 创建ThreadLocal变量
static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void main(String[] args) {
// 在主线程中设置变量的值
localVariable.set("Hello Java");
// 创建线程1
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
// 子线程输出变量的值
System.out.println("threadOne:" + localVariable.get());
}
});
// 开启线程
threadOne.start();
// 主线程输出变量的值
System.out.println("main:" + localVariable.get());
}
}
输出结果如下 :
main:Hello Java
threadOne:null
我们可以看到,主线程中设置了变量的值,在子线程中是获取不到的
然后我们修改上面代码中创建变量的方法:
// 创建ThreadLocal变量
static ThreadLocal<String> localVariable = new InheritableThreadLocal<>();
接下来再运行一遍,看一下结果:
main:Hello Java
threadOne:Hello Java
可以看到,子线程中也获取到了变量的值
因为在父线程创建子线程时,构造函数会把父线程中inheritableThreadLocals变量里面的副本变量复制一份保存到子线程的inheritableThreadLocals变量里面。
注意:ThreadLocal使用完后,一定要删除,否则会造成内存泄漏,平时的时候可能察觉不到,但当数据量很大的时候,就会出现内存泄漏的情况,而且也会影响业务逻辑。