记得在一次面试中被问到 ThreadLocal, 答得马马虎虎, 所以打算研究一下 ThreadLocal 的源码
面试官 : 用过 ThreadLocal 吗?
楼主答 : 用过, 当时使用 ThreadLocal 的时候, 使用 Spring 实现横切整个 Controller 层, 使用 ThreadLocal 实现了统计每次请求对应方法的执行时间, 具体代码如下
- public class ProfilerAdvice {Logger logger = Logger.getLogger(ProfilerAdvice.class);
- private static final ThreadLocal<Long> TIME_THREADLOCAL = new ThreadLocal<Long>(){
- @Override
- protected Long initialValue() {
- return System.currentTimeMillis();
- }
- };
- public static final void begin() {
- TIME_THREADLOCAL.set(System.currentTimeMillis());
- }
- public static final Long end() {
- return System.currentTimeMillis() - TIME_THREADLOCAL.get();
- }
- public void requestStart() {
- ProfilerAdvice.begin();
- }
- public void requestEnd(JoinPoint joinPoint) {
- /**
- * 获取被拦截的方法.
- */
- String methodName = joinPoint.getSignature().getName();
- logger.info(methodName + "方法请求耗时"+ProfilerAdvice.end()/1000.0+"s");
- }
- }
面试官 : 那 ThreadLocal 对应的数据结构是咋样的啊?
楼主答 : 应该是使用哈希表实现的吧, 楼主这个时候心里开始没底了, 然后就没有然后......
聊聊 JDK 源码中 ThreadLocal 的实现
主要方法:
ThreadLocal 的 get 方法
ThreadLocal 之 get 流程:
1, 获取当前线程 t;
2, 返回当前线程 t 的成员变量 ThreadLocalMap(以下简写 map);
3,map 不为 null, 则获取以当前线程为 key 的 ThreadLocalMap 的 Entry(以下简写 e), 如果 e 不为 null, 则直接返回该 Entry 的 value;
4, 如果 map 为 null 或者 e 为 null, 返回 setInitialValue()的值. setInitialValue()调用重写的 initialValue()返回新值(如果没有重写 initialValue 将返回默认值 null), 并将新值存入当前线程的 ThreadLocalMap(如果当前线程没有 ThreadLocalMap, 会先创建一个).
- 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();
- }
ThreadLocal 的 set 方法
ThreadLocal 之 set 流程:
1, 获取当前线程 t;
2, 返回当前线程 t 的成员变量 ThreadLocalMap(以下简写 map);
3,map 不为 null, 则更新以当前线程为 key 的 ThreadLocalMap, 否则创建一个 ThreadLocalMap, 其中当前线程 t 为 key;
- 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 主要的代码实现
下面代码时楼主认为 ThreadLocal 中比较重要的, 还是比较容易看懂的, 就不在一一细说
- public class ThreadLocal<T> {
- /**
- * ThreadLocals 依赖于附加的每线程线性探测哈希映射到每个线程(Thread.threadLocals 和
- * inheritableThreadLocals). ThreadLocal 对象充当键, 通过 threadLocalHashCode 搜索. 这是一个自定义哈希码
- * (仅在 ThreadLocalMaps 内有用), 可以消除哈希冲突
- * 在连续构造 ThreadLocals 的常见情况下
- * 由相同的线程使用, 同时保持良好的行为和异常情况的发生.
- */
- private final int threadLocalHashCode = nextHashCode();
- /**
- * 下一个哈希码将被发出, 原子更新, 从零开始.
- */
- private static AtomicInteger nextHashCode =
- new AtomicInteger();
- /**
- * 连续生成的散列码之间的区别 , 将隐式顺序线程本地 ID 转换为近乎最佳的散布
- * 两倍大小的表的乘法散列值.
- */
- private static final int HASH_INCREMENT = 0x61c88647;
- /**
- * Returns the next hash code.
- */
- private static int nextHashCode() {
- return nextHashCode.getAndAdd(HASH_INCREMENT);
- }
- /**
- * 返回此线程局部变量的当前线程的 "初始值". 在线程首次访问带有 {@link #get} 方法的变量时, 将调用此方法,
- * 除非线程先前调用了 {@link #set} 方法, 在这种情况下, initialValue 方法不会 为该线程调用.
- * 通常, 每个线程最多调用一次此方法, 但在后续调用 {@link #remove} 后跟 {@link #get} 时可能会再次调用此方法.
- * <p > 这个实现只是返回{@code null}; 如果程序员希望线程局部变量的初始值不是{@code null},
- * 则必须对子代码 {@CodeLocal} 进行子类化, 并重写此方法. 通常, 将使用匿名内部类.
- */
- protected T initialValue() {
- return null;
- }
- 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();
- }
- 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;
- }
- public void set(T value) {
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- createMap(t, value);
- }
- /**
- * SuppliedThreadLocal 是 JDK8 新增的内部类, 只是扩展了 ThreadLocal 的初始化值的方法而已
- * , 允许使用 JDK8 新增的 Lambda 表达式赋值. 需要注意的是, 函数式接口 Supplier 不允许为 null.
- */
- static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
- private final Supplier<? extends T> supplier;
- SuppliedThreadLocal(Supplier<? extends T> supplier) {
- this.supplier = Objects.requireNonNull(supplier);
- }
- @Override
- protected T initialValue() {
- return supplier.get();
- }
- }
- /**
- * ThreadLocalMap 是定制的 hashMap, 关于 HashMap 的更详细的问题请参看聊聊 HashMap 源码,
- * 仅用于维护当前线程的本地变量值. 仅 ThreadLocal 类对其有操作权限,
- * 是 Thread 的私有属性. 为避免占用空间较大或生命周期较长的数据常驻于内存引发一系列问题,
- * hash table 的 key 是弱引用 WeakReferences. 当空间不足时, 会清理未被引用的 entry.
- */
- 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;
- }
- }
- /**
- * 构造一个新的包含初始映射, ThreadLocal 映射的新映射, 因此我们只在创建至少一个条目时创建一个.
- */
- ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
- table = new Entry[INITIAL_CAPACITY];
- int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
- table[i] = new Entry(firstKey, firstValue);
- size = 1;
- setThreshold(INITIAL_CAPACITY);
- }
- /**
- * 从给定的 parentMap 构造一个包含所有 map 的新 ThreadLocal. 仅由 createInheritedMap 调用.
- *
- * @param parentMap the map associated with parent thread.
- */
- private ThreadLocalMap(ThreadLocalMap parentMap) {
- Entry[] parentTable = parentMap.table;
- int len = parentTable.length;
- setThreshold(len);
- table = new Entry[len];
- for (int j = 0; j <len; j++) {
- Entry e = parentTable[j];
- if (e != null) {
- @SuppressWarnings("unchecked")
- ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
- if (key != null) {
- Object value = key.childValue(e.value);
- Entry c = new Entry(key, value);
- int h = key.threadLocalHashCode & (len - 1);
- while (table[h] != null)
- h = nextIndex(h, len);
- table[h] = c;
- size++;
- }
- }
- }
- }
- }
- }
ThreadLocal 使用 demo
- public class ThreadLocalExample {
- public static class MyRunnable implements Runnable {
- private ThreadLocal<Integer> threadLocal =
- new ThreadLocal<Integer>();
- @Override
- public void run() {
- threadLocal.set( (int) (Math.random() * 100D) );
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- }
- System.out.println(threadLocal.get());
- }
- }
- public static void main(String[] args) {
- MyRunnable sharedRunnableInstance = new MyRunnable();
- Thread thread1 = new Thread(sharedRunnableInstance);
- Thread thread2 = new Thread(sharedRunnableInstance);
- thread1.start();
- thread2.start();
- thread1.join(); //wait for thread 1 to terminate
- thread2.join(); //wait for thread 2 to terminate
- }
- }
本示例创建一个传递给两个不同线程的 MyRunnable 实例. 两个线程都执行 run()方法, 从而在 ThreadLocal 实例上设置不同的值. 如果对 set()调用的访问已经同步, 并且它不是 ThreadLocal 对象, 则第二个线程将覆盖第一个线程设置的值.
但是, 由于它是一个 ThreadLocal 对象, 因此两个线程无法看到对方的值. 因此, 他们设定并获得不同的价值观.
小结
ThreadLocal 特性及使用场景
1, 实现单个线程单例以及单个线程上下文信息存储, 比如交易 id 等
2, 实现线程安全, 非线程安全的对象使用 ThreadLocal 之后就会变得线程安全, 因为每个线程都会有一个对应的实例
3, 承载一些线程相关的数据, 避免在方法中来回传递参数
ThreadLocal 使用过程中出现的问题
1,ThreadLocal 并未解决多线程访问共享对象的问题, 如果 ThreadLocal.set()的对象是多线程共享的, 那么还是涉及并发问题;
2, 会导致内存泄露么?
有人认为 ThreadLocal 会导致内存泄露, 原因如下
首先 ThreadLocal 实例被线程的 ThreadLocalMap 实例持有, 也可以看成被线程持有.
如果应用使用了线程池, 那么之前的线程实例处理完之后出于复用的目的依然存活
所以, ThreadLocal 设定的值被持有, 导致内存泄露.
上面的逻辑是清晰的, 然而 Java 的设计者早已经想到了这个问题, ThreadLocal 并不会产生内存泄露, 因为 ThreadLocalMap 在选择 key 的时候,
并不是直接选择 ThreadLocal 实例, 而是 ThreadLocal 实例的弱引用. 所以实际上从 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;
- }
- }
- }
参考文章
Java ThreadLocal http://tutorials.jenkov.com/java-concurrency/threadlocal.html
理解 Java 中的 ThreadLocal https://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
来源: https://juejin.im/entry/5af3028e6fb9a07ac85a6a24