ThreadLocal的核心原理

注:本博文内容来源于冰河大牛所著图书《深入理解高并发编程:核心原理与案例实战》

ThreadLocal能够保证每个线程操作的都是本地内存中的变量副本。在底层实现上,调用ThreadLocal的set()方法会将本地变量保存在具体线程的内存空间中,而ThreadLocal并不负责存储具体的数据。

1.Thread类源码

public class Thread implements Runnable {
  /***********省略N行代码*************/
  ThreadLocal.ThreadLocalMap threadLocals = null;
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  /***********省略N行代码*************/
}

Thread类中维护者ThreadLocalMap类型的两个成员变量threadLocalsinheritableThreadLocals,初始值都为null。当前线程第一次调用ThreadLocal的set()方法或者get()方法时才会实例化变量。
每个线程的本地变量不是存放在ThreadLocal实例里面的,而是存放在调用线程的threadLocals变量里面

调用ThreadLocal类里面的set()方法将本地变量存储在具体线程的内存空间中,而ThreadLocal类只是提供了set()和get()方法来存储和读取本地变量的值,

当调用ThreadLocal类的set()方法时,把要存储的值放入调用线程的threadLocals中存储起来,
当调用ThreadLocal类的get()方法时,从当前线程的threadLocals变量中将存储的值取出来。 

1.1 set()方法

public void set(T value) {
  //获取当前线程
  Thread t = Thread.currentThread();
  //以当前线程为Key,获取ThreadLocalMap对象
  ThreadLocalMap map = getMap(t);
  //获取的ThreadLocalMap对象不为空
  if (map != null)
    //设置value的值
    map.set(this, value);
  else
    //获取的ThreadLocalMap对象为空,创建Thread类中的threadLocals变量
    createMap(t, value);
}

先获取调用set()方法的线程,然后用该线程作为Key来获取ThreadLocalMap对象
getMap(Thread t)的方法源码如下所示。

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

getMap(Thread t)方法获取的是线程变量自身的threadLocals成员变量。

在set()方法中,如果调用getMap(t)方法返回的对象不为空,则把value值设置到Thread类的threadLocals成员变量中,而传递的key为当前ThreadLocal的this对象,value就是通过set()方法传递的值。
如果调用getMap(t)方法返回的对象为空,则程序调用createMap(t, value)方法来创建当前线程的threadLocals变量。

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

1.2 get()方法

public T get() {
  //获取当前线程
  Thread t = Thread.currentThread();
  //获取当前线程的threadLocals成员变量
  ThreadLocalMap map = getMap(t);
  //获取的threadLocals变量不为空
  if (map != null) {
    //返回本地变量对应的值
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
      @SuppressWarnings("unchecked")
      T result = (T)e.value;
      return result;
   }
 }
  //初始化threadLocals成员变量的值
  return setInitialValue();
}

通过当前线程来获取threadLocals,如果threadLocals不为空,则直接返回当前线程绑定的本地变量,否则调
用setInitialValue()方法初始化threadLocals的值。

private T setInitialValue() {
  //调用初始化Value的方法
  T value = initialValue();
  Thread t = Thread.currentThread();
  //根据当前线程获取threadLocals成员变量
  ThreadLocalMap map = getMap(t);
  if (map != null)
      //threadLocals不为空,则设置value值
    map.set(this, value);
  else
    //threadLocals为空,创建threadLocals变量
    createMap(t, value);
  return value;
}

initialValue()方法的源码如下所示。

protected T initialValue() {
  return null;
}

initialValue()方法可以由子类覆写,在ThreadLocal类中这个方法直接返回null。

1.3 remove()方法

public void remove() {
  //根据当前线程获取threadLocals成员变量
  ThreadLocalMap m = getMap(Thread.currentThread());
  if (m != null)
    //threadLocals成员变量不为空,则移除value值
    m.remove(this);
}

remove()方法的实现:首先根据当前线程获取threadLocals,若不为空,则直接移除value的值。
注意:如果调用线程一直不终止,则本地变量会一直存放在调用线程的threadLocals成员变量中,所以,如果不需要使用本地变量时,可以通过调用ThreadLocal的remove()方法,将本地变量从当前线程的threadLocals成员变量中删除,以免出现内存溢出的问题。

ThreadLocal变量不具有传递性

同一个ThreadLocal在父线程中设置值后,在子线程中无法获取到

public class ThreadLocalTest {
  private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();
  public static void main(String[] args){
    //在主线程中设置值
    threadLocal.set("ThreadLocalTest");
    //在子线程中获取值
        Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("子线程获取值:" + threadLocal.get());
     }
   });
    //启动子线程
    thread.start();
    //在主线程中获取值
    System.out.println("主线程获取值:" + threadLocal.get());
 }
}
//运行这段代码输出的结果信息如下所示。
//主线程获取值:ThreadLocalTest
//子线程获取值:null

我们可以使用InheritableThreadLocal来解决传递性的问题。

InheritableThreadLocal使用示例

InheritableThreadLocal类继承自ThreadLocal类,它能够让子线程访问到在父线程中设置的本地变量的值,当new的对象是InheritableThreadLocal时:

public class ThreadLocalTest {
  private static ThreadLocal<String> threadLocal = new InheritableThreadLocal<String>();
  public static void main(String[] args){
    //在主线程中设置值
    threadLocal.set("ThreadLocalTest");
    //在子线程中获取值
    Thread thread = new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println("子线程获取值:" + threadLocal.get());
     }
   });
    //启动子线程
    thread.start();
    //在主线程中获取值
    System.out.println("主线程获取值:" + threadLocal.get());
 }
}

//此时,运行程序输出的结果信息如下所示。
//主线程获取值:ThreadLocalTest
//子线程获取值:ThreadLocalTest

  • 可以看到,使用InheritableThreadLocal类存储本地变量时,子线程能够获取到父线程中设置的本地变量。

2. InheritableThreadLocal原理

下InheritableThreadLocal类的源码,如下所示。

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  protected T childValue(T parentValue) {
    return parentValue;
 }
  ThreadLocalMap getMap(Thread t) {
   return t.inheritableThreadLocals;
 }
  void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
 }
}

InheritableThreadLocal类继承自ThreadLocal类,并且重写了ThreadLocal类的childValue()方法、getMap()方法和createMap()方法。
这里,我们需要思考一个问题:InheritableThreadLocal类的childValue()方法是何时被调用的呢?这就需要我们来看下
Thread类的构造方法了,如下所示。

public Thread() {
  init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
  init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
  init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
  init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
  init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
  init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
  init(null, target, name, 0);
  }
public Thread(ThreadGroup group, Runnable target, String name) {
  init(group, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
       long stackSize) {
  init(group, target, name, stackSize);
}
  

可以看到,Thread类的构造方法最终调用的是init()方法,那我们就来看下init()方法,如下所示.

private void init(ThreadGroup g, Runnable target, String name,
           long stackSize, AccessControlContext acc,
           boolean inheritThreadLocals) {
   /************省略部分源码************/
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)
      this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    /* Stash the specified stack size in case the VM cares */
    this.stackSize = stackSize;
    /* Set thread ID */
    tid = nextThreadID();
 }

可以看到,在init()方法中会判断传递的inheritThreadLocals变量是否为true,同时父线程中的inheritableThreadLocals是否为null,如果传递的inheritThreadLocals变量为true,同时,父线程中的inheritableThreadLocals不为null,则调用ThreadLocal类的createInheritedMap()方法。

static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
  return new ThreadLocalMap(parentMap);
}

在createInheritedMap()中,使用父线程的inheritableThreadLocals变量作为参数创建新的ThreadLocalMap对象。然后在Thread类的init()方法中会将这个ThreadLocalMap对象赋值给子线程的inheritableThreadLocals成员变量。
接下来,我们来看看ThreadLocalMap的构造函数都干了啥,如下所示。

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) {
        //调用重写的childValue方法
        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++;
     }
   }
 }
}

在ThreadLocalMap的构造函数中,调用了InheritableThreadLocal类重写的childValue()方法。而InheritableThreadLocal类通过重写getMap()方法和createMap()方法,让本地变量保存到了Thread线程的inheritableThreadLocals变量中,线程通过InheritableThreadLocal类的set()方法和get()方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。此时,如果父线程创建子线程,在Thread类的构造函数中会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals变量中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值