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
来进行维护的。可以设置和获取副本变量的值。