为线程中一个本地变量的副本提供索引,ThreadLocal能够用来维护与当前线程相关的一些上下文,不须要经过每一个方法调用将其做为参数传递。bash
使用threadLocal必定要注意内存泄漏,不然仍是建议定义context类,保存每一个线程自身上下文多线程
API
四个主要方法:优化
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
复制代码
每个线程均可以独立地改变本身的副本,而不会影响其余线程所对应的副本。ui
内部存在一个ThreadLocalMap,key为ThreadLocal自身,值为线程局部变量spa
初始时,在Thread里面,threadLocals(ThreadLoaclMap的实例)为空,当经过ThreadLocal变量调用get()方法时,就会对Thread类中的threadLocals进行初始化,而且以当前ThreadLocal变量为key,使用默认初始化的value做为值,存到threadLocals。线程
注意: 这里初始化的value默认是null,若是get以前没有调用set方法,会报空指针异常,咱们建立ThreadLocal变量是能够重写initialValue()方法。3d
private static ThreadLocal<String> strThreadLocal = new ThreadLocal<String>() {
public String initialValue() {
return "default";
}
};
复制代码
tips:
四种类型的引用指针
强引用
new 对象
Object obj = new Object();
正常垃圾回收code
软引用
内存空间不够,垃圾回收时会回收其内存
Object obj = new Object();
SoftReference sf = new SoftReference(obj);
obj = null;
sf.get();//有时候会返回nullcdn
弱引用
弱引用的对象,能够存活到下一次垃圾回收
Object obj = new Object();
WeakReference wf = new WeakReference(obj);
obj = null;
wf.get();//有时候会返回null
wf.isEnQueued();//返回是否被垃圾回收器标记为即将回收的垃圾
虚应用
不影响对象的生命周期。
虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,若是发现它还有虚引用,就会在回收对象的内存以前,把这个虚引用加入到与之关联的引用队列中。
若是程序发现某个虚引用已经被加入到引用队列,那么就能够在所引用的对象的内存被回收以前采起必要的行动。
内存泄漏: 原本应该被回收的对象由于某种缘由没法被回收
ThreadLocal 中的ThreadLocMap(后面简称为map),它会在线程运行的整个周期都会存在,若是map中的key和value是强引用,那么map持有它们的引用,引用的ThreadLocal的对象被回收了,可是整个Entry仍是存在,形成内存泄漏。
对此,Threadlocal有如下优化:
在多线程不断set放入一个大对象的时候,其中一个线程运行周期比较长,在这期间没有发生gc,ThreadLocalMap中的key就不会及时回收,或者程序运行异常中断,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
复制代码