解密ThreadLocal

转自:http://qifuguang.me/2015/09/02/%5BJava%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83%5D%E8%A7%A3%E5%AF%86ThreadLocal/

相信读者在网上也看了很多关于ThreadLocal的资料,很多博客都这样说:ThreadLocal为解决多线程程序的并发问题提供了一种新的思路;ThreadLocal的目的是为了解决多线程访问资源时的共享问题。如果你也这样认为的,那现在给你10秒钟,清空之前对ThreadLocal的错误的认知!

看看JDK中的源码是怎么写的:

This class provides thread-local variables. These variables differ from
their normal counterparts in that each thread that accesses one (via its
{@code get} or {@code set} method) has its own, independently initialized
copy of the variable. {@code ThreadLocal} instances are typically private
static fields in classes that wish to associate state with a thread (e.g.,
a user ID or Transaction ID).

翻译过来大概是这样的(英文不好,如有更好的翻译,请留言说明):

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get或set方法访问)时能保证各个线程里的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程的上下文。

可以总结为一句话:ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

如何实现的

看了基本介绍,也看了最简单的效果演示之后,我们更应该好好研究下ThreadLocal内部的实现原理。如果给你设计,你会怎么设计?相信大部分人会有这样的想法:

每个ThreadLocal类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。

没错,这是最简单的设计方案,JDK最早期的ThreadLocal就是这样设计的。JDK1.3(不确定是否是1.3)之后ThreadLocal的设计换了一种方式。

我们先看看JDK8的ThreadLocal的 get方法的源码:

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的源码:

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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;
}

createMap函数的源码:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

简单解析一下,get方法的流程是这样的:

  1. 首先获取当前线程
  2. 根据当前线程获取它的ThreadLocalMap
  3. 如果获取的Map不为空,则在Map中以ThreadLocal对象的引用作为key来在Map中获取对应的Entry e,否则转到5
  4. 如果e不为null,则返回e.value,否则转到5
  5. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

然后需要注意的是Thread类中包含一个成员变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

所以,可以总结一下ThreadLocal的设计思路:
每个Thread维护一个ThreadLocalMap映射表,这个映射表的key是ThreadLocal实例本身,value是真正需要存储的Object。
这个方案刚好与我们开始说的简单的设计方案相反。查阅了一下资料,这样设计的主要有以下几点优势:

  • 这样设计之后每个Map的Entry数量变小了:之前是Thread的数量,现在是ThreadLocal的数量,能提高性能,据说性能的提升不是一点两点(没有亲测)
  • 当Thread销毁之后对应的ThreadLocalMap也就随之销毁了,能减少内存使用量。
-----------------------------------------------------  华丽的分割线  --------------------------------------------------------


ThreadLocal类提供了ThreadLocalMap的定义,并且提供了操作ThreadLocalMap的方法。

ThreadLocalMap类中有一个Entry[]数组,通过ThreadLocal对象和Entry[]数组的size可以最终定位Entry[]数组中的某个ThreadLocalMap.Entry对象。

Thread类中有一个default访问级别的ThreadLocalMap属性。该属性保存了该线程独有的个本地变量。

ThreadLocalMap是最终保存数据的地方,同时它将ThreadLocal和Thread关联起来,所以ThreadLocalMap才是这个ThreadLocal机制的中心、主角。

ThreadLocal的get()方法首先获得当前线程t,通过t获得当前线程的ThreadLocalMap属性值,然后通过(调用get()方法的)ThreadLocal对象来定位ThreadLocalMap中的Entry[]数组的某个值,最终返回Entry中的value。


类中1个ThreadLocal + 系统n个线程:每个线程的ThreadLocalMap中的Entry[]数组中只有一个Entry值。该Entry值的位置由ThreadLocal对象和数组size共同确定。

类中m个ThreadLocal + 系统1个线程:该线程的ThreadLocalMap中的Entry[]数组中有m个Entry值。通过某个ThreadLocal对象和数组size可以定位该线程下该ThreadLocal对象所对应的Entry,然后取出Entry中的value,该value就是该线程下该ThreadLocal所保存的值。

类中m个ThreadLocal + 系统n个线程:每个线程的ThreadLocalMap中的Entry[]数组中有m个Entry值。每个线程通过某个ThreadLocal对象和数组size可以定位该线程下该ThreadLocal对象所对应的Entry,然后取出Entry中的value。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值