ThreadLocal
- ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。
- 正是因为这是一个线程的局部变量,也就是说,只有在当前线程内部可以访问,因此是线程安全的,但是要注意,如果在应用上为每一个线程分配了相同的对象实例,那么ThreadLocal也不能保证线程安全。
initialValue函数
initialValue函数用来设置ThreadLocal的初始值,函数签名如下:
protected T initialValue(){
return null;}
该函数在调用get函数的时候会第一次调用,但是如果一开始就调用了set函数,则该函数不会被调用。通常该函数只会被调用一次,除非手动调用了remove函数之后又调用get函数,这种情况下,get函数中还是会调用initialValue函数。该函数是protected类型的,很显然是建议在子类重载该函数的,所以通常该函数都会以匿名内部类的形式被重载,以指定初始值,比如:
public class TestThreadLocal{
private static final Thread<Integer> value = new ThreadLocal<Integer>(){
protected Integer initialValue{
return Integer.valueOf(1);}};}
- get函数 该函数用来获取与当前线程关联的ThreadLocal的值,如果当前线程没有该ThreadLocal的值,则调用initialValue函数获取初始值返回。
- set函数 set函数用来设置当前线程的该ThreadLocal的值,设置当前线程的ThreadLocal的值为value。
remove函数 emove函数用来将当前线程的ThreadLocal绑定的值删除。 下面是一段使用ThreadLocal对5个线程各自内部的变量value,循环往value值相加数字
private static final ThreadLocal<Integer> value = new ThreadLocal<Integer>(){
protected Integer initialValue(){
return 0;
}
};
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i = 0;i<5;i++){
new Thread(new MyThread(i)).start();
}
}
static class MyThread implements Runnable{
private int index;
public MyThread(int index){
this.index = index;
}
public void run(){
System.out.println("线程"+index+"的初始calue:"+value.get());
for(int i = 0;i<10;i++){
value.set(value.get()+i);
}
System.out.println("线程"+index+"的累加value:"+value.get());
}
}
最终运行结果如图所示:
ThreadLocal内部实现原理
get方法流程:
- 首先获取当前线程
- 根据当前线程获取一个Map
- 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的value e,否则转到5
- 如果e不为null,则返回e.value,否则转到5
- Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map
总结一下ThreadLocal的实际设计思路: 每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object 注意:ThreadLocalMap是使用ThreadLocal的弱引用作为Key的。对象之间的引用关系图如图所示:
所以有人认为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 永远无法回收,造成内存泄露。
但根据JDK的ThreadLocalMap源码,可知:
- ThreadLocalMap的getEntry函数的流程为:
- 首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (len-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;
- 如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry,否则,如果key值为null,则擦除该位置的Entry,否则继续向下一个位置查询
- 在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。