ThreadLocal:线程本地变量。ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
适用:1 需要各个线程间独立的变量,2 同线程下多方法传递的变量(解决某些方法无法相互传参)
1 ThreadLocal 适合解决共享/单例对象 的成员变量,在多线程下保持各个线程独立性
2 类的静态ThreadLocal,注意同线程变量覆盖问题
3 ThreadLocal初始化时引用全局变量/共享变量,则任然是线程共享的,需要同步
4 可以作为线程上下文,方便同线程在多个方法栈中使用,但是要注意跨线程/进程之间丢失与传递
方法中的局部变量,保存在各个访问线程栈中,相互独立不存在线程安全问题,但是注意传入参数/或是方法过程中调用的对象引用和变量是不是多线程共享的
包含如下方法
void set(T value) 设置当前线程的线程局部变量的值。
public T get() 该方法返回当前线程所对应的线程局部变量。
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue() 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
例如:
class A{
int x=0;
ThreadLocal<integer> y= new ThreadLocal<integer>(){
@Override
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 否则会NULL指针错误
public integer initialValue() {
return 0 ;
} ;
}
A a= new A()
当A类的对象a作为多个线程的共享变量时,a.x为共享成员变量,线程之间相互竞争,需要lock来保证线程安全。a.y为线程本地变量,每个线程访问a时,y使用的是线程自己的副本,各个线程之间互不影响。ThreadLocal 以Thread.currentThread()为依据获取其对应副本。
实际用例:
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
public class ThreadLocal测试 {
public static void main(String[] args) {
One one=new One(); // 主线程 和T1线程 共享对象
one.mapAdd("main", "1");
one.mapAdd("main1", "2");
Thread t1=new Thread(new RunW(one),"T1");
t1.start();
one.showMap();
}
}
class One{
//线程本地变量ThreadLocal
//通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 否则会NULL指针错误
private ThreadLocal<HashMap<String, String>> map=new ThreadLocal<HashMap<String, String>>() {
@Override
public HashMap<String, String> initialValue() {
return new HashMap<String, String>();
}
};
public HashMap<String, String> getMap() {
return map.get();
}
public void mapAdd(String K,String V) {
this.map.get().put(K, V);
}
public void showMap() {
HashMap<String, String> lMap=this.map.get();
Set set=lMap.entrySet();
Iterator<Entry<String, String>> iterator= set.iterator();
while (iterator.hasNext()) {
System.out.println(Thread.currentThread().getName()+" "+iterator.next() );
}
}
}
class RunW implements Runnable{
private One one;
public RunW(One one) {
super();
this.one = one;
}
@Override
public void run() {
one.mapAdd("T1", "T1");
one.mapAdd("T11", "T2");
one.showMap();
}
}
每个线程中可以持有很多个ThreadLocal对象,这些对象通过hash后存储在Thread的ThreadLocalMap中,其中的Key为ThreadLocal对象,
注意若将ThreadLocal对象以static修饰时,同一线程访问此类不同对象时,ThreadLocal会被覆盖
public class ThreadLocal测试2 {
public static void main(String[] args) throws InterruptedException {
WorkTest w1=new WorkTest("1");
WorkTest w2=new WorkTest("2");
w2.set();
w1.set(); //w1.set() 会覆盖 w2.set() 的ThreadLocal<String>的值
w2.get();
w1.get();
}
}
class WorkTest {
private static ThreadLocal<String> tl=new ThreadLocal<String>() {
@Override
protected String initialValue() {return null;};
};
private String name;
public WorkTest(String name) {
super();
this.name=name;
}
public void set() {
this.tl.set(Thread.currentThread().getName()+name);
}
public void get() {
System.out.println(Thread.currentThread().getName()+" "+this.tl.get());
}
}
输出结果:
main main1
main main1
w2.set()的操作被后边的w1.set()覆盖
value为该对象在本线程中的一个副本。用图表示如下:
从图中可以看出,ThreadLocal本身并不存储value值,只是作为key在ThreadLocalMap中索引value值
那么实现机制是如何的呢?
1、每个Thread对象内部都维护了一个ThreadLocalMap这样一个ThreadLocal的Map,可以存放若干个ThreadLocal。
1 2 3 |
|
2、当我们在调用get()方法的时候,先获取当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
1 2 3 4 5 6 7 8 9 10 |
|
3、当我们调用set()方法的时候,很常规,就是将值设置进ThreadLocal中。
4、总结:当我们调用get方法的时候,其实每个当前线程中都有一个ThreadLocal。每次获取或者设置都是对该ThreadLocal进行的操作,是与其他线程分开的。
5、应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。
6、其实说再多也不如看一下源码来得清晰。如果要看源码,其中涉及到一个WeakReference和一个Map,这两个地方需要了解下,这两个东西分别是a.Java的弱引用,也就是GC的时候会销毁该引用所包裹(引用)的对象,这个threadLocal作为key可能被销毁,但是只要我们定义成他的类不卸载,tl这个强引用就始终引用着这个ThreadLocal的,永远不会被gc掉。b.和HashMap差不多。
事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。
内存泄漏问题
ThreadLocal是被ThreadLocalMap以弱引用的方式关联着,因此如果ThreadLocal没有被ThreadLocalMap以外的对象引用,则在下一次GC的时候,ThreadLocal实例就会被回收,那么此时ThreadLocalMap里的一组KV的K就是null了,因此在没有额外操作的情况下,此处的V便不会被外部访问到,而且只要Thread实例一直存在,Thread实例就强引用着ThreadLocalMap,因此ThreadLocalMap就不会被回收,那么这里K为null的V就一直占用着内存。
如果不及时remove则只要线程存活则Value一直无法GC。建议在过滤器、拦截器、AOP中使用时,在退出方法中清除ThreadLocal,保证每一次线程调用最终出栈时都能清除ThreadLocal。