许多人喜欢把ThreadLocal和多线程放在一起,其实这两者并没有太大的联系。ThreadLocal其实是提供了一个线程范围的上下文引用。如果你需要在两个没有关联的对象或者两个不互相调用的平行的方法之间共用一些线程级别的参数,那么就可以使用ThreadLocal。
从内存角度理解的话,其实一个线程就是一个虚拟机栈中的栈,这个线程调用的方法就是这个栈的栈帧。线程对方法的开始调用和调用结束分别对应着栈帧的入栈和出栈。栈帧里边包含了方法的局部变量表等元素,两个方法之间如果没有调用关系的话显然是无法共享局部变量和参数的,而ThreadLocal的访问范围是整个线程栈,所以两个没用调用关系的方法之间可以通过ThreadLocal来完成数据的共享,由于是在一个线程里边共享数据,显然不会有多线程并发的问题,可以安全的使用。
要理解ThreadLocal的实现其实很简单,只要把握好ThreadLocal、Thread、ThreadLocalMap三个类之间的关系即可。ThreadLocalMap虽然是定义在ThreadLocal里边的静态内部类,但是其实是Thread持有一个ThreadLocalMap对象,而ThreadLocalMap里边存放的就是以ThreadLocal为key的键值对。在第一次调用ThreadLocal的get或者set的时候调用setInitialValue对Thread的ThreadLocalMap赋予初值(setInitialValue只会被调用一次,get和set谁先被调用就由谁完成初始化),key毫无疑问就是你使用的那个ThreadLocal对象,而value的初值默认是null,可以通过覆写ThreadLocal的initialValue方法来返回一个自定义的初值。如
ThreadLocal<Integer> t = new ThreadLocal() {
protected Integer initialValue() {
return 10;
}
};
注意,一个线程的ThreadLocalMap只会初始化一次,一个ThreadLocal在一个线程中也只会有一个值,多个ThreadLocal在一个线程中是存放在同一个ThreadLocalMap里边的。ThreadLocalMap的内部实现也比较简单,HashMap的内部数据结构是数组和单链表的组合,而ThreadLocalMap的内部结构仅仅是一个数组,如果两个ThreadLocal通过ThreadLocalMap的算法算出来的在数组中的位置是同一个值i的话(即这两个ThreadLocal在ThreadLocalMap的数组中应该放在同一个位置table[i]),那么后放入的那个ThreadLocal在数组中的实际位置是计算出来的i值往后移一位即为table[i+1],如果table[i+1]处也已经有值了,那就继续往后移动一位直到找到没有存放值的位置为止。所以ThreadLocalMap在get的时候也是按照这种方式从计算出来的i值开始往后一位位的找直到找到需要的值或者null为止。其实ThreadLocalMap的set和get都比较简单,但是在set和get的时候会触发清理无效数据的动作,所以看起源码来会比较头大。
其实只要知道什么时候用ThreadLocal以及把握好ThreadLocalMap、ThreadLocal、Thread这三个类之间的关系基本上就可以了。
从内存角度理解的话,其实一个线程就是一个虚拟机栈中的栈,这个线程调用的方法就是这个栈的栈帧。线程对方法的开始调用和调用结束分别对应着栈帧的入栈和出栈。栈帧里边包含了方法的局部变量表等元素,两个方法之间如果没有调用关系的话显然是无法共享局部变量和参数的,而ThreadLocal的访问范围是整个线程栈,所以两个没用调用关系的方法之间可以通过ThreadLocal来完成数据的共享,由于是在一个线程里边共享数据,显然不会有多线程并发的问题,可以安全的使用。
要理解ThreadLocal的实现其实很简单,只要把握好ThreadLocal、Thread、ThreadLocalMap三个类之间的关系即可。ThreadLocalMap虽然是定义在ThreadLocal里边的静态内部类,但是其实是Thread持有一个ThreadLocalMap对象,而ThreadLocalMap里边存放的就是以ThreadLocal为key的键值对。在第一次调用ThreadLocal的get或者set的时候调用setInitialValue对Thread的ThreadLocalMap赋予初值(setInitialValue只会被调用一次,get和set谁先被调用就由谁完成初始化),key毫无疑问就是你使用的那个ThreadLocal对象,而value的初值默认是null,可以通过覆写ThreadLocal的initialValue方法来返回一个自定义的初值。如
ThreadLocal<Integer> t = new ThreadLocal() {
protected Integer initialValue() {
return 10;
}
};
注意,一个线程的ThreadLocalMap只会初始化一次,一个ThreadLocal在一个线程中也只会有一个值,多个ThreadLocal在一个线程中是存放在同一个ThreadLocalMap里边的。ThreadLocalMap的内部实现也比较简单,HashMap的内部数据结构是数组和单链表的组合,而ThreadLocalMap的内部结构仅仅是一个数组,如果两个ThreadLocal通过ThreadLocalMap的算法算出来的在数组中的位置是同一个值i的话(即这两个ThreadLocal在ThreadLocalMap的数组中应该放在同一个位置table[i]),那么后放入的那个ThreadLocal在数组中的实际位置是计算出来的i值往后移一位即为table[i+1],如果table[i+1]处也已经有值了,那就继续往后移动一位直到找到没有存放值的位置为止。所以ThreadLocalMap在get的时候也是按照这种方式从计算出来的i值开始往后一位位的找直到找到需要的值或者null为止。其实ThreadLocalMap的set和get都比较简单,但是在set和get的时候会触发清理无效数据的动作,所以看起源码来会比较头大。
其实只要知道什么时候用ThreadLocal以及把握好ThreadLocalMap、ThreadLocal、Thread这三个类之间的关系基本上就可以了。