面试必备之ThreadLocal

概述

通常情况下,一个类的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。

采用空间换时间,用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为「线程封闭」。

ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本(局部变量)。

分析

ThreadLocal 是一个泛型类,其提供的主要方法如下:

// 获取值,如果没有返回 null
public T get()
public void set(T)
// 提供初始值,当调用 get 方法时如果之前没有设置过则会调用该方法获取初始值,默认为 null
protected T initialValue()
// 删掉当前线程对应的值,如果删掉后再次调用get,则会再调用initialValue获取初值
public void remove()

其中set方法源码:

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocal类中的getMap方法

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
// Thread类中声明的threadLocals变量
ThreadLocal.ThreadLocalMap threadLocals = null;

其中,ThreadLocalMap
每个线程维护一个ThreadLocalMap映射表,映射表的key是ThreadLocal实例,使用的是ThreadLocal的弱引用 ,value是具体需要存储的Object。有ThreadLocalMap的内部类,该类为一个采用线性探测法实现的HashMap。它的key为ThreadLocal对象而且还使用WeakReference,ThreadLocalMap正是用来存储变量副本的。

实心箭头表示强引用,空心箭头表示弱引用:

问题
ThreadLocal没有外部强引用,当发生垃圾回收时,这个ThreadLocal一定会被回收(弱引用,不管当前内存空间足够与否,GC时都会被回收),这样就会导致ThreadLocalMap中出现key为null的Entry,外部将不能获取这些key为null的Entry的value,并且如果当前线程一直存活,那么就会存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,导致value对应的Object一直无法被回收,产生内存泄露。ThreadLocal的get、set和remove方法都实现对所有key为null的value的清除,但仍可能会发生内存泄露,因为可能使用ThreadLocal的get或set方法后发生GC,此后不调用get、set或remove方法,为null的value就不会被清除。

解决方法:

  1. 在使用完后,在remove
  2. private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉

其中 static 是为了确保全局只有一个保存 String 对象的 ThreadLocal 实例;final 确保 ThreadLocal 的实例不可更改,防止被意外改变,导致放入的值和取出来的不一致,另外还能防止 ThreadLocal 的内存泄漏

用处:保存线程的独立变量。对一个线程类(继承自Thread)
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。

ThreadLocal是一个为线程提供线程局部变量的工具类。为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程。ThreadLocal提供的只是一个浅拷贝,如果变量是一个引用类型,那么就要考虑它内部的状态是否会被改变,想要解决这个问题可以通过重写ThreadLocal的initialValue()函数来自己实现深拷贝,建议在使用ThreadLocal时一开始就重写该函数。

用途

  1. 实现单个线程单例以及单个线程上下文信息存储。ThreadLocal在Spring中处处可见:管理Request作用域中的Bean、事务管理、任务调度、AOP等模块。Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作。
  2. 非线程安全的对象使用ThreadLocal之后就会变得线程安全。如SimpleDateFormat,SDF,在多线程并发的情况下是线程不安全的,可能会抛出NumberFormatException或其它异常。使用ThreadLocal包装,直接创建一个共享实例对象,每个线程都有自己的SDF实例对象。
// SDF非线程安全的原因:CalendarBuilder类的establish方法部分代码
Calendar establish(Calendar cal) {
    // 省略部分不重要代码
    cal.clear();
    // 省略部分不重要代码
    return cal;
}
  1. 承载一些线程相关的数据,避免在方法中来回传递参数

ThreadLocal vs Synchronized

都能实现多线程环境下的共享变量的线程安全,区别呢?

Synchronized通过锁(同步)来实现内存共享,ThreadLocal为每个线程维护一个本地变量,即通过避免对象的共享来实现。锁更强调的是如何同步多个线程去正确地共享一个变量,ThreadLocal则是为了解决同一个变量如何不被多个线程共享。如果锁机制是用时间换空间的话,那么ThreadLocal就是用空间换时间。

同步机制是为了同步多线程对相同资源的并发访问,是为了线程间数据共享的问题,而 ThreadLocal 是隔离多线程数据共享,从根本上就不在多个线程之间共享资源,这样自然就不需要多线程的同步机制。

拓展

基于ThreadLocal思想的拓展类有很多。

ThreadLocalRandom

待学习

InheritableThreadLocal

在子线程中获取主线程threadLocal中set方法设置的值,如何实现?
使用InheritableThreadLocal。通过ThreadLocal threadLocal = new InheritableThreadLocal(),在子线程中就可以通过get方法获取到主线程set方法设置的值。
InheritableThreadLocal继承ThreadLocal重写childValuegetMapcreateMap方法:

/**
 * 在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
 */
protected T childValue(T parentValue) {
    return parentValue;
}
/**
 * 重写getMap,操作InheritableThreadLocal时,将只影响Thread类中的inheritableThreadLocals变量,与threadLocals变量不再有关系
 */
ThreadLocalMap getMap(Thread t) {
    return t.inheritableThreadLocals;
}
/**
 * 类似于getMap,操作InheritableThreadLocal时,将只影响Thread类中的inheritableThreadLocals变量,
 * 与threadLocals变量不再有关系
 */
void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}

当使用InheritableThreadLocal创建实例对象时,当前线程Thread对象中维护一个inheritableThreadLocals变量,也是ThreadLocalMap类型,在创建子线程的过程中,将主线程维护的inheritableThreadLocals变量的值复制到子线程维护的inheritableThreadLocals变量中,这样子线程就可以获取到主线程设置的值。

应用
调用链追踪:在调用链系统设计中,为了优化系统运行速度,会使用多线程编程,为了保证调用链ID能够自然的在多线程间传递,需要考虑ThreadLocal传递问题。

TransmittableThreadLocal

阿里开源,GitHub

TransmittableThreadLocal详解
增强版的ThreadLocal-TransmittableThreadLocal

FastThreadLocal

源自Netty,
Netty中FastThreadLocal源码分析

参考

正确理解Thread Local的原理与适用场景
深入研究java.lang.ThreadLocal类

原创文章 131 获赞 175 访问量 32万+

猜你喜欢

转载自blog.csdn.net/lonelymanontheway/article/details/105856675