正确理解 ThreadLocal

Java 的世界中有一道由 ThreadLocal 组成的墙,外面的人想进去,里面的人想出来。

今天就带你打开 ThreadLocal 的大门,并且希望你可能会得到一些不一样的东西。

什么是 ThreadLocal ?

看一看 JDK 官方怎么说?

这个类提供了线程局部变量(thread-local variables)。这些变量与普通变量的不同之处在于,每个访问该变量的线程(通过其get或set方法)都有自己的、独立初始化的变量副本

ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程联系起来(例如,用户ID或事务ID)。

例如,下面的类产生了每个线程的本地唯一标识符。一个线程的ID在它第一次调用ThreadId.get()时被分配,并在后续调用中保持不变。

   import java.util.concurrent.atomic.AtomicInteger;
  
   public class ThreadId {
       // Atomic integer containing the next thread ID to be assigned
       private static final AtomicInteger nextId = new AtomicInteger(0);
  
       // Thread local variable containing each thread's ID
       private static final ThreadLocal<Integer> threadId =
           new ThreadLocal<Integer>() {
               @Override protected Integer initialValue() {
                   return nextId.getAndIncrement();
           }
       };
  
       // Returns the current thread's unique ID, assigning it if necessary
       public static int get() {
           return threadId.get();
       }
   }

只要线程还活着,并且ThreadLocal实例可被访问,每个线程都持有对其线程-本地变量副本的隐式引用;在一个线程离开后,其所有线程-本地实例的副本都会被垃圾回收(除非存在对这些副本的其他引用)。


可以看出几点:

  1. ThreadLocal 的使用形态:通常以私有静态字段形式出现。
  2. 使用 ThreadLocal 的场景是:使状态(即数据)和线程关联(可以理解数据线程隔离,至于怎么实现隔离的,后面会分析)。
  3. 如何理解每个线程都拥有自己独立变化的副本这句话?我想很多人都理解错了,后面分析。

ThreadLocal 的常用方法简介

  1. public void set(T value)

    故名思意,这个方法是用来存值的。

  2. public T get()

    依然故名思意,这个方法是用来取值的。取哪里的值呢?可以取上面通过 set 方法设置的值。

  3. public void remove()

    依然故名思意,这个方法是用例清空(清空哪里的值呢?后续分析)值的,清空之后 get() 方法默认返回 null;

ThreadLocal 的常用方法源码分析

简单从源码的角度分析一下,上述常用方法的实现原理。

提示:请带着问题看源码分析或者看完回头回答如下问题,以检验你对 ThreadLocal 的理解程度:

  1. ThreadLocal 和 Thread 的关系是什么?
  2. 数据存储在哪里?
  3. ThreadLocal 是个 map 结构嘛?
  4. 数据存储在 ThreadLocal 中嘛?
  5. ThreadLocal 实例本身会变嘛?
  6. 一个线程可以绑定多个 ThreadLocal 嘛?
  7. 多个线程共享同一个 ThreadLocal 实例嘛?
ThreadLocal 源码概览

简单看一下源码的样子,只是看一下方法的样子,一回生二回熟,后面详细解读,给你带来不一样的体验,结论说不定会冲击到你哦。

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

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    public T get() {
        Thread t = Thread.currentThread();//1.
        ThreadLocalMap map = getMap(t);//2.
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);//3.
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
      
    public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
}
public void set(T value) 分析

使用 ThreadLocal 在当前线程上设置值

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

// 2.1
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
  1. 获取当前线程(虽然简单,但重要,后面可以发现线程其实才是数据的载体)

  2. 获取当前线程所绑定的一个类Map对象(关键字:该线程绑定的对象),以下简称map。

    • 2.1 可以看到,直接返回的是 Thread 对象上的一个 ThreadLocalMap 类型的变量

    • 那么,是不是可以体会到线程隔离的意思了?数据存储在指定线程内,当然是线程隔离的了。

  3. 将 value 设置进 map,key 为 ThreadLocal (划重点啦)实例对象,value 为你要存储的值。

  4. 初始化一个 map,并执行步骤 3 设置值。

public T get() 分析

使用 ThreadLocal 获取绑定在当前线程的值

public T get() {
    Thread t = Thread.currentThread();//1.
    ThreadLocalMap map = getMap(t);//2.
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//3.
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();// 4.
}
  1. 获取当前变量
  2. 获取当前线程所绑定的一个类Map对象,以下简称map,同 set 方法。
  3. 以 ThreadLocal 作为 key 从 map 中取出其绑定的值,有没有感觉,ThreadLocal 就是线程数据绑定的标识而已?
  4. 如果没有设置过值(或者被 remove 过了),那么执行初始化动作。
public void remove() 分析

清空当前线程 在 ThreadLocal 上绑定的值

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());//1.
    if (m != null)
        m.remove(this);
}
  1. 获取当前线程所绑定的一个类Map对象

  2. 以 ThreadLocal 对象作为 key,删除其绑定的值

ThreadLocal 小结

  • ThreadLocal 其实并不存储数据,真正的数据存储在线程中
  • 之所以可以做到数据线程之间隔离,只是因为数据存储在指定线程内部,并不是 ThreadLocal 有什么魔法能力
  • 所以,ThreadLocal 也并没有什么变量拷贝一份,只是作为一个 key 的作用,用来关联其绑定的值
  • 所以,ThreadLocal 并不是解决的线程之间数据共享的问题,而是更加保守的感觉,线程私有
  • 使用 ThreadLocal 设置值时,key 是 ThreadLocal 对象本身,key 在多个线程间是共享的
  • 所以,ThreadLocal 本省既不存储数据,也没有变化
  • 所以,一个线程可以对应多个 ThreadLocal 对象
  • 可以将 ThreadLocal 简单理解为绑定数据的“记号”

灵魂发问:ThreadLocal 的真正意图是什么???

这个问题抛出的原因是,你在网络上搜索 ThreadLocal 进行学习时,总有会看到头部大佬的文章。

而有些人的文章里,好像会向你传递一个信息:使用 ThreadLocal 可以节约资源,只使用少量变量对象即可。

这对吗?

大佬们常用的例子大多和 事务连接SimpleDateFormat 相关

常用话术:SimpleDateFormat,每次使用都new一个多浪费性能呀…所以引出了 ThreadLocal

# 例子可能会是这样
class ThreadSafeFormatter {
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
}

这个例子,确实是有用的,利用了 ThreadLocal 的什么能力呢?

实际上是数据线程绑定,得以让对象在整个线程内使用,请再次体会以下。

那么,使用 ThreadLocal 的这些场景,真正意图是什么呢?

实际上,它使得一个对象在整个线程生命周期内共享,而不用线程执行过程中的每个方法用到了都去 new 一个,可以在一定程度上复用对象。

另外,有一点比较重要的作用,可以使对象的传递更加优雅,对程序透明。比如,在事务方法内,我们并没有关心过事务对象的产生与传递,但是实际上框架就是通过 ThreadLocal 提供的能力,在整个线程执行中偷偷传递了,在生成的时候 set,在需要的时候 get。


你 Get 到了吗?

祝你打的愉快!😄

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值