ThreadLocal是一个线程内部数据存储的工具类。
在每一个线程中都有一个ThreadLocal.ThreadLocalMap类型的变量threadLocals,用于存放自己线程的一些数据,其它线程不能对此变量进行访问。对于同一个static ThreadLocal,不同线程只能从get,set,remove方法来获取自己的变量值,这样的操作并不影响其他线程。主要有以下几个方法:
-
ThreadLocal.get():获取ThreadLocal中当前线程副本变量的值。 -
ThreadLocal.set():设置ThreadLocal中当前线程副本变量的值。 -
ThreadLocal.remove():移除ThreadLocal中当前线程副本变量的值。 -
ThreadLocal.initialValue():ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。
看下面这个例子:
public class ThreadLocalClass {
private static ThreadLocal<Object> mThreadLocal = new ThreadLocal<Object>(){
/**
* mThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
* @return
*/
@Override
protected Object initialValue() {
System.out.println("调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!");
return super.initialValue();
}
};
public static void main(String[] args) {
final ThreadLocalClass mThreadLocalClass = new ThreadLocalClass();
Thread threadA = new Thread(new MyTaskA("MyTaskA"));
threadA.start();
try {
threadA.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程threadA执行完成后-->value:"+ mThreadLocal.get());
Thread threadB = new Thread(new MyTaskB("MyTaskB"));
threadB.start();
try {
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程threadB执行完成后-->value:"+ mThreadLocal.get());
}
private static class MyTaskA implements Runnable{
String name;
public MyTaskA(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("MyTaskA线程执行前-->"+ name+":"+ mThreadLocal.get());
for (int i = 0; i < 5; i++) {
if (null == mThreadLocal.get()){
mThreadLocal.set(0);
System.out.println("线程"+ name+":"+ mThreadLocal.get());
}else{
int getValue = (int) mThreadLocal.get();
mThreadLocal.set(getValue+1);
System.out.println("线程"+ name+","+ mThreadLocal.get());
if (i == 2){
mThreadLocal.remove();
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyTaskA线程-->"+ name+":"+ mThreadLocal.get());
}
}
private static class MyTaskB implements Runnable{
String name;
public MyTaskB(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("MyTaskB线程执行前-->"+ name+":"+ mThreadLocal.get());
for (int i = 0; i < 5; i++) {
if (null == mThreadLocal.get()){
mThreadLocal.set("A"+i);
System.out.println("线程"+ name+":"+mThreadLocal.get());
}else{
String getValue = (String) mThreadLocal.get();//获取共享变量
mThreadLocal.set(getValue+"B");//设置共享变量值
System.out.println("线程"+ name+","+ mThreadLocal.get());
if (i == 2){
mThreadLocal.remove();//清除共享变量
}
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("MyTaskB线程-->"+ name+":"+ mThreadLocal.get());
}
}
}
运行结果为:
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
MyTaskA线程执行前-->MyTaskA:null
线程MyTaskA:0
线程MyTaskA,1
线程MyTaskA,2
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程MyTaskA:0
线程MyTaskA,1
MyTaskA线程-->MyTaskA:1
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程threadA执行完成后-->value:null
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
MyTaskB线程执行前-->MyTaskB:null
线程MyTaskB:A0
线程MyTaskB,A0B
线程MyTaskB,A0BB
调用get方法时,当前线程共享变量没有设置,调用initialValue获取默认值!
线程MyTaskB:A3
线程MyTaskB,A3B
MyTaskB线程-->MyTaskB:A3B
线程threadB执行完成后-->value:null
这样就看出来ThreadLocal值之间有没有相互影响
在一个线程中,这样的共享变量值可以有多个。看下面的例子:
public class ThreadLocalClass {
private ThreadLocal<Long> mLongThreadLocal = new ThreadLocal<Long>(){
/**
* mLongThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
* @return
*/
@Override
protected Long initialValue() {
return 0L;
}
};
private ThreadLocal<String> mStringThreadLocal = new ThreadLocal<String>(){
/**
* mStringThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
* @return
*/
@Override
protected String initialValue() {
return "initValue";
}
};
public static void main(String[] args) {
final ThreadLocalClass mThreadLocalClass = new ThreadLocalClass();
mThreadLocalClass.set();
System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
Thread thread1 = new Thread(new MyTaskC(mThreadLocalClass, "MyTaskC"));
thread1.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Thread:CurrThread:"+ Thread.currentThread().getName());
System.out.println("Thread:Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
mThreadLocalClass.set();
System.out.println("Thread:Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
}
});
thread2.start();
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Long:"+ mThreadLocalClass.getLong()+", String:"+ mThreadLocalClass.getString());
}
private void set(){
mLongThreadLocal.set(Thread.currentThread().getId());
mStringThreadLocal.set(Thread.currentThread().getName());
}
private Long getLong(){
return mLongThreadLocal.get();
}
private String getString(){
return mStringThreadLocal.get();
}
private static class MyTaskC implements Runnable{
String name;
ThreadLocalClass threadLocal;
public MyTaskC(ThreadLocalClass threadLocal, String name){
this.name = name;
this.threadLocal = threadLocal;
}
@Override
public void run() {
System.out.println("MyTaskC-->CurrThread:"+ Thread.currentThread().getName());
System.out.println("MyTaskC:Long:"+ threadLocal.getLong()+", String:"+ threadLocal.getString());
threadLocal.set();
System.out.println("MyTaskC:Long:"+ threadLocal.getLong()+", String:"+ threadLocal.getString());
}
}
}
运行结果为:
Long:1, String:main//主线程中输出
MyTaskC-->CurrThread:Thread-0//子线程名称
MyTaskC:Long:0, String:initValue//调用get方法设置初值
MyTaskC:Long:11, String:Thread-0//子线程输出
Long:1, String:main//子线程执行完成后,在主线程中输出
Thread:CurrThread:Thread-1//子线程
Thread:Long:0, String:initValue//调用get方法设置初值
Thread:Long:12, String:Thread-1//子线程输出
Long:1, String:main//子线程执行完成后,在主线程中输出
上面这个例子在线程中生成了两个fuben变量mLongThreadLocal,mStringThreadLocal,这两个值在主线程和两个子线程中的输出互不影响。
总结一下:
- 通过
ThreadLocal创建的变量,都存储在每个线程自己的参数threadLocals中。 - 通过
ThreadLocal创建的变量,可以有多个。 - 通过
ThreadLocal创建的变量,必须先set方法后在调用get方法,除非重写initialValue方法。因为先调用get方法会报空指针异常,这个异常来源于初始化的默认值为null。
我们来看看这个类的源码实现
-
get源码实现
1 public T get() {
2 Thread var1 = Thread.currentThread();
3 ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
4 if(var2 != null) {
5 ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
6 if(var3 != null) {
7 Object var4 = var3.value;
8 return var4;
9 }
10 }
11 return this.setInitialValue();
}
第二行代码是获取当前线程,第三行是通过方法this.getMap(var1)返回ThreadLocal.ThreadLocalMap类型的map值,第四到十行就是根据这个map值不为空时,取出对应的值,第11行如果map为空则调用方法setInitialValue返回值。
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
return var1.threadLocals;
}
从上面代码看,getMap方法就是返回了当前线程的threadLocals参数值。进入这个参数:
ThreadLocalMap threadLocals = null;
可见它是个ThreadLocalMap类型的值,它是ThreadLocal类的内部类。我们在来看ThreadLocalMap的实现:
static class ThreadLocalMap {
......
ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
this.size = 0;
this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
int var3 = var1.threadLocalHashCode & 15;
this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
this.size = 1;
this.setThreshold(16);
}
......
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> var1, Object var2) {
super(var1);
this.value = var2;
}
}
......
}
它的构造方法中第一个参数以ThreadLocal为参数,在ThreadLocalMap的内部类Entry中,继承与WeakReference并以ThreadLocal为键。这里只有key为若引用,而value为强引用;而key使用若引用后,生命周期只能到下次GC之前。
这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
那么怎么解决这个问题呢?就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。
1 private T setInitialValue() {
2 Object var1 = this.initialValue();
3 Thread var2 = Thread.currentThread();
4 ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
5 if(var3 != null) {
6 var3.set(this, var1);
7 } else {
8 this.createMap(var2, var1);
9 }
10 return var1;
}
第2行直接调用初始化方法initialValue得到一个Object对象值,第3行获取当前线程,第4行得到ThreadLocal.ThreadLocalMap类型的map值,如果此值不为空,则设置键值对,为空,则新建。
初始化initialValue方法代码为:
protected T initialValue() {
return null;
}
默认情况下,初值为null。新建方法createMap代码为:
void createMap(Thread var1, T var2) {
var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
}
这样就生成了ThreadLocal.ThreadLocalMap对象。
-
set源码实现:
public void set(T var1) {
Thread var2 = Thread.currentThread();
ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
if(var3 != null) {
var3.set(this, var1);
} else {
this.createMap(var2, var1);
}
}
-
remove源码实现:
public void remove() {
ThreadLocal.ThreadLocalMap var1 = this.getMap(Thread.currentThread());
if(var1 != null) {
var1.remove(this);
}
}
从上面的源码分析来看,在Thread中有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,它就是用来存储真正的ThreadLocal副本变量的,以当前ThreadLocal为键,以T类型的变量为value;
起初时,Thread中的成员变量threadLocals值为空,通过ThreadLocal共享变量调用get或者set方法后,变量threadLocals开始被初始化,并且以当前的ThreadLocal为键,以要保存的值为value保存到threadLocals之中。然后在当前线程中,如果要使用ThreadLocal共享变量就可以使用get,set或者remove方法来进行操作了。
归纳一下也就是:
- 每个线程中都有一个
ThreadLocal.ThreadLocalMap类型的成员变量threadLocals。 -
threadLocals里面存储的是线程的本地对象(key)和线程的变量副本(value) - 每个线程中的
threadLocals变量,都是由工具类ThreadLocal来进行维护的。可以设置和获取副本变量的值。