ThreadLocal使用详解
一、ThreadLocal的使用场景为线程中一个本地变量的副本提供索引,ThreadLocal可以用来维护与当前线程相关的一些上下文,不需要通过每个方法调用将其作为参数传递。使用threadLocal一定要注意内存泄漏,否则还是建议定义context类,保存每个线程自身上下文二、ThreadLocal分析API四个主要方法:public T get() { }public void ...
一、ThreadLocal的使用场景
线程局部变量。为线程中一个本地变量的副本提供索引,ThreadLocal可以用来维护与当前线程相关的一些上下文,不需要通过每个方法调用将其作为参数传递。
使用threadLocal一定要注意内存泄漏,否则还是建议定义context类,保存每个线程自身上下文
二、ThreadLocal分析
API
四个主要方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }

-
每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。
-
Thread内部存在一个ThreadLocalMap,key为ThreadLocal自身,值为线程局部变量
-
初始时,在Thread里面,threadLocals(ThreadLoaclMap的实例)为空,当通过ThreadLocal变量调用get()方法时,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为key,使用默认初始化的value作为值,存到threadLocals。
注意: 这里初始化的value默认是null,如果get之前没有调用set方法,会报空指针异常,我们创建ThreadLocal变量是可以重写initialValue()方法。
private static ThreadLocal<String> strThreadLocal = new ThreadLocal<String>() {
public String initialValue() {
return "default";
}
};
三、ThreadLocal内存泄漏
tips:
四种类型的引用
-
强引用
new 对象
Object obj = new Object();
正常垃圾回收 -
软引用
内存空间不够,垃圾回收时会回收其内存
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
obj = null;
sf.get();//有时候会返回null -
弱引用
弱引用的对象,可以存活到下一次垃圾回收
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾 -
虚应用
不影响对象的生命周期。
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。
如果程序发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。

内存泄漏: 本来应该被回收的对象因为某种原因无法被回收
为什么使用弱引用 ?
注意上图两条引用链,ThreadLocalMap的生命周期跟Thread一样长
- key使用强引用: 引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏
- key使用弱引用: 引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除
使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏
为什么会内存泄漏?
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏
Threadlocal有以下优化:
- map中key使用弱引用,在一次gc后,可以会被自动回收,变成null
- 每次调用set,get,remove方法会自动清除value对应为空的对象
即使有这样的优化也会存在问题:
- 线程运行周期长。在多线程不断set放入一个大对象的时候,其中一个线程运行周期比较长,在这期间没有发生gc,ThreadLocalMap中的key就不会及时回收。假设使用线程池操作threadlocal对象,由于核心线程不会被回收,线程一直持有threadlocalMap对象,可能会造成内存泄漏
- 程序运行异常中断,set之后再也不会调用threadLocal相关方法,都会造成内存泄漏。
为了避免内存泄漏,建议使用remove方法:
//伪代码
try {
threadLocal.set()
业务逻辑...
threadLocal.get()
} catch(){
} finally{
threadLocal.remove()
}
简单代码示例:
public class Test {
ThreadLocal<String> stringLocal = new ThreadLocal<String>() {
public String initialValue() {
return "default";
}
};
ThreadLocal<Long> longLocal = new ThreadLocal<Long>() {
public Long initialValue() {
return 0L;
}
};
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public void get() {
System.out.println("longLocal: " + longLocal.get());
System.out.println("stringLocal: " + stringLocal.get());
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
System.out.println("获取默认值");
test.get();
System.out.println("----------");
System.out.println("主线程:");
test.set();
test.get();
System.out.println("----------");
//开启一个线程
Thread t1 = new Thread() {
public void run() {
System.out.println("t1线程:");
test.set();
test.get();
}
};
t1.start();
t1.join();
System.out.println("-----------");
System.out.println("验证主线程:");
test.get();
//在主线程和t1线程中,各自创建了一个变量副本,stringLocal ,longLocal不一样
}
}
结果:
获取默认值
longLocal: 0
stringLocal: default
----------
主线程:
longLocal: 1
stringLocal: main
----------
t1线程:
longLocal: 11
stringLocal: Thread-0
-----------
验证主线程:
longLocal: 1
stringLocal: main
更多推荐
所有评论(0)