ThreadLocal 类的介绍
ThreadLocal, 从表面上读英文的意思为线程本地变量, 这样也许更好理解了, 就是每个线程自己独有的, 不与其它线程共享的变量.
常用的就这几个, 俩内部类, 四个方法.
get()方法是用来获取 ThreadLocal 在当前线程中保存的变量副本.
set()用来设置当前线程中变量的副本.
remove()用来移除当前线程中变量的副本.
initialValue()是一个 protected 方法, 一般是用来在使用时进行重写的, 它是一个延迟加载方法. ThreadLocal 没有被当前线程赋值时或当前线程刚调用 remove 方法后调用 get 方法, 返回此方法值.
举例:
定义两个不同任务的线程, 分别向各自的本地变量中存放值, 见证两个线程本地变量中的内容是互不干扰的.
- public class MyThreadLocal {
- // 采用匿名内部类的方式来重写 initialValue 方法
- private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>() {
- /**
- * ThreadLocal 没有被当前线程赋值时或当前线程刚调用 remove 方法后调用 get 方法, 返回此方法值
- */
- @Override
- protected Object initialValue() {
- System.out.println("调用 get 方法时, 当前线程共享变量没有设置, 调用 initialValue 获取默认值!");
- return null;
- }
- };
- // 操纵 int 类型的任务线程
- public static class MyIntegerTask implements Runnable {
- private String name;
- MyIntegerTask(String name) {
- this.name = name;
- }
- public void run() {
- for (int i = 0; i <5; i++) {
- // ThreadLocal.get 方法获取线程变量
- if (null == MyThreadLocal.threadLocal.get()) {
- // ThreadLocal.et 方法设置线程变量
- MyThreadLocal.threadLocal.set(0);
- System.out.println("线程" + name + ": 0");
- } else {
- int num = (Integer) MyThreadLocal.threadLocal.get();
- MyThreadLocal.threadLocal.set(num + 1);
- System.out.println("线程" + name + ":" + MyThreadLocal.threadLocal.get());
- if (i == 3) {
- MyThreadLocal.threadLocal.remove();
- }
- }
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- // 操纵 string 类型的任务线程
- public static class MyStringTask implements Runnable {
- private String name;
- MyStringTask(String name) {
- this.name = name;
- }
- public void run() {
- for (int i = 0; i < 5; i++) {
- if (null == MyThreadLocal.threadLocal.get()) {
- MyThreadLocal.threadLocal.set("a");
- System.out.println("线程" + name + ": a");
- } else {
- String str = (String) MyThreadLocal.threadLocal.get();
- MyThreadLocal.threadLocal.set(str + "a");
- System.out.println("线程" + name + ":" + MyThreadLocal.threadLocal.get());
- }
- try {
- Thread.sleep(800);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- public static void main(String[] args) {
- new Thread(new MyIntegerTask("IntegerTask1")).start();
- new Thread(new MyStringTask("StringTask1")).start();
- }
- }
运行结果:
调用 get 方法时, 当前线程共享变量没有设置, 调用 initialValue 获取默认值!
线程 IntegerTask1: 0
调用 get 方法时, 当前线程共享变量没有设置, 调用 initialValue 获取默认值!
线程 StringTask1: a
线程 StringTask1: aa
线程 IntegerTask1: 1
线程 StringTask1: aaa
线程 IntegerTask1: 2
线程 StringTask1: aaaa
线程 IntegerTask1: 3
线程 StringTask1: aaaaa
调用 get 方法时, 当前线程共享变量没有设置, 调用 initialValue 获取默认值!
线程 IntegerTask1: 0
get 方法分析
涉及到的源码:
get(); 方法: 供 ThreadLocal 对象来调用
- public T get() {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null) {
- @SuppressWarnings("unchecked")
- T result = (T)e.value;
- return result;
- }
- }
- return setInitialValue();
- }
getMap(); 方法: 这个方法是返回当前线程 t 中的一个成员变量 threadLocals, 它是 Thread 类中的一个内部类
- ThreadLocal.ThreadLocalMap threadLocals = null;
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
看一下 ThreadLocalMap 的实现: 可以看到 ThreadLocalMap 的 Entry 继承了 WeakReference(弱引用类), 并且使用 ThreadLocal 作为键值.
- static class ThreadLocalMap {
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
- .........
- }
getEntry(); 方法
- private Entry getEntry(ThreadLocal<?> key) {
- int i = key.threadLocalHashCode & (table.length - 1);
- Entry e = table[i];
- if (e != null && e.get() == key)
- return e;
- else
- return getEntryAfterMiss(key, i, e);
- }
setInitialValue(); 方法: 就是如果 map 不为空, 就设置键值对, 为空, 再创建 Map
- private T setInitialValue() {
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- return value;
- }
createMap(); 方法: 如果 map 为空, 就初始化 ThreadLocalMap, 哈哈明白了~
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap(this, firstValue);
- }
不难看出, get()方法, 首先获取当前的线程, 然后通过 getMap(t)方法获取到一个 map,map 的类型为 ThreadLocalMap. 然后接着下面获取到 < key,value > 键值对, 如果获取成功, 则返回 value 值, 如果 map 为空, 则调用 setInitialValue 方法返回 value.
测试完上面的例子, 看完 get 方法的实现, 应该明白 ThreadLocal 是怎么个原理了, 大致如下 (就是这样的): 首先, 在每个线程 Thread 内部有一个 ThreadLocal.ThreadLocalMap 类型的成员变量 threadLocals, 这个 threadLocals 就是用来存储实际的变量的, 键值为当前 ThreadLocal 变量, value 为变量(比如说上面定义的 String 变量或者 Integer 变量). 初始时, 在 Thread 里面, threadLocals 为空, 当通过 ThreadLocal 变量调用 get() 方法或者 set()方法, 就会对 Thread 类中的 threadLocals 进行初始化, 并且以当前 ThreadLocal 变量为键值, 以 ThreadLocal 要保存的变量为 value, 存到 threadLocals. 如果要使用副本变量, 就可以通过 get 方法在 threadLocals 里面查找.
这种存储结构的好处:
线程死去的时候, 线程共享变量 ThreadLocalMap 则销毁.
ThreadLocalMap<ThreadLocal,Object > 键值对数量为 ThreadLocal 的数量, 一般来说 ThreadLocal 数量很少, 相比在 ThreadLocal 中用 Map<Thread, Object > 键值对存储线程共享变量(Thread 数量一般来说比 ThreadLocal 数量多), 性能提高很多.
ThreadLocal 的应用场景
最常见的 ThreadLocal 使用场景为 用来解决 数据库连接, Session 管理等.
比如以下, 代码来自: https://www.iteye.com/topic/103804
- private static final ThreadLocal threadSession = new ThreadLocal();
- public static Session getSession() throws InfrastructureException {
- Session s = (Session) threadSession.get();
- try {
- if (s == null) {
- s = getSessionFactory().openSession();
- threadSession.set(s);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
关于 ThreadLocal 导致的内存泄漏问题, 和解决办法
每个 thread 中都存在一个 map,map 的类型是 ThreadLocal.ThreadLocalMap. Map 中的 key 为一个 threadlocal 实例. 这个 Map 的确使用了弱引用, 不过弱引用只是针对 key. 每个 key 都弱引用指向 threadlocal, 当把 threadlocal 实例置为 null 以后, 没有任何强引用指向 threadlocal 实例, 所以 threadlocal 将会被 gc 回收. 但是 value 却不能回收, 因为存在一条从 currentThread 连接过来的强引用. 只有当前 thread 结束以后, currentThread 才不会存在栈中, 强引用断开, CurrentThread,Map,value 将全部被 GC 回收.
所以得出一个结论就是只要这个线程对象被 gc 回收, 就不会出现内存泄露, 但在 threadLocal 设为 null 和线程结束这段时间不会被回收的, 就发生了我们认为的内存泄露. 其实这是一个对概念理解的不一致, 也没什么好争论的. 最要命的是线程对象不被回收的情况, 这就发生了真正意义上的内存泄露. 比如使用线程池的时候, 线程结束是不会销毁的, 会再次使用的. 就可能出现内存泄露.
Java 为了最小化减少内存泄露的可能性和影响, 在 ThreadLocal 的 get,set 的时候都会清除线程 Map 里所有 key 为 null 的 value. 所以最怕的情况就是, threadLocal 对象设 null 了, 开始发生 "内存泄露", 然后使用线程池, 这个线程结束, 线程放回线程池中不销毁, 这个线程一直不被使用, 或者分配使用了又不再调用 get,set 方法, 那么这个期间就会发生真正的内存泄露.
一般有两种方法:
使用完线程共享变量后, 显示调用 ThreadLocalMap.remove 方法清除线程共享变量
JDK 建议 ThreadLocal 定义为 private static, 这样 ThreadLocal 的弱引用问题则不存在了.
来源: http://www.jianshu.com/p/e31fbd50b05f