ThreadLocal原理分析

接下来我的的学习方向偏向于 Android & Java 面试相关知识点系统性的总结,欢迎关注。java

ThreadLocal类是java.lang包下的一个类,用于线程内部的数据存储,经过它能够在指定的线程中存储数据,本文针对该类进行原理分析。android

经过思惟导图对其进行简单的总结:git

一.ThreadLocal源码分析

ThreadLocal类最重要的几个方法以下:github

  • get():T 获取当前线程下存储的变量副本
  • set(T):void 存储该线程下的某个变量副本
  • remove():void 移除该线程下的某个变量副本

1.get()方法分析

ThreadLocal类比较简单,其最重要的就是get()set()方法,顾名思义,起做用就是取值和设置值:面试

// 获取当前线程中的变量副本
public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取线程中的ThreadLocalMap对象
    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;
        }
    }
    // 若没有该变量副本,返回setInitialValue()
    return setInitialValue();
}
复制代码

这里先将ThreadLocalMap暂时理解为一个Map结构的容器,内部存储着该线程做用域下的的全部变量副本,咱们从ThreadLocal类中取值的时候,其实是从ThreadLocalMap中取值。算法

若是Map中没有该变量的副本,会从setInitialValue()中取值:app

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;
}
复制代码

能够看到,setInitialValue()中也很是的简单,依然是从当前线程中获取到ThreadLocalMap,略微不一样的是,setInitialValue()会对变量进行初始化,存入ThreadLocalMap中并返回。源码分析

这个初始化的方法的执行,须要开发者本身重写initialValue()方法,不然返回值依然为null学习

public class ThreadLocal<T> {
    // ...
    protected T initialValue() {
       return null;
    }
}    
复制代码

2.set()方法分析

setInitialValue()方法相似,set()方法也很是简单:this

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map不为空,直接将ThreadLocal对象做为key
        // 变量自己的值为value,存入map
        map.set(this, value);
    else
        // 不然,建立ThreadLocalMap
        createMap(t, value);
}
复制代码

能够看到,这个方法的做用就是将变量副本做为value存入Map,须要注意的是,key并不是是咱们下意识认为的Thread对象,而是ThreadLocal自己(ThreadValue自己是一对一的,咱们更容易将其映射为key-value的关系)。

3.remove()方法分析

public void remove() {
   ThreadLocalMap m = getMap(Thread.currentThread());
   if (m != null)
       m.remove(this);
}
复制代码

对于变量副本的移除,也是经过map进行处理的,和set()get()相同,Entry的键值对中,ThreadLocal自己做为key,对变量副本进行检索。

4.小结

能够看出,ThreadLocal自己内部的逻辑都是围绕着ThreadLocalMap在运做,其自己更像是一个空壳,仅做为API供开发者调用,内部逻辑都委托给了ThreadLocalMap

接下来咱们来探究一下ThreadLocalMapThread以及ThreadLocal之间的关系。

2、ThreadLocalMap分析

ThreadLocalMap内部代码和算法相对复杂,我的亦是只知其一;不知其二,所以就不逐行代码进行分析,仅系统性进行概述。

首先来看一下ThreadLocalMap的定义:

public class ThreadLocal<T> {

    // ThreadLocalMap是ThreadLocal的内部类
    static class ThreadLocalMap {

      // Entry类,内部key对应的是ThreadLocal的弱引用
      static class Entry extends WeakReference<ThreadLocal<?>> {
          // 变量的副本,强引用
          Object value;

          Entry(ThreadLocal<?> k, Object v) {
              super(k);
              value = v;
          }
      }
    }
}
复制代码

ThreadLocal中的嵌套内部类ThreadLocalMap本质上是一个map,依然是key-value的形式,其中有一个内部类Entry,其中key能够看作是ThreadLocal实例的弱引用。

和最初的设想不一样的是,ThreadLocalMapkey并不是是线程的实例Thread,而是ThreadLocal,那么ThreadLocalMap是如何保证同一个Thread中,ThreadLocal的指定变量惟一呢?

// 1.ThreadLocal的set()方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    // ...
}

// 2.getMap()其实是从Thread中获取threadLocals成员
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

public class Thread implements Runnable {
    // 3.每一个Thread实例都持有一个ThreadLocalMap的属性
    ThreadLocal.ThreadLocalMap threadLocals = null;
}
复制代码

Thread自己持有ThreadLocal.ThreadLocalMap的属性,每一个线程在向ThreadLocalsetValue的时候,其实都是向本身的ThreadLocalMap成员中加入数据;get()同理。

3、内存泄漏的风险?

在上一小节中,咱们看到ThreadLocalMap中的Entry中,其ThreadLocal做为key,是做为弱引用进行存储的。

ThreadLocal再也不被做为强引用持有时,会被GC回收,这时ThreadLocalMap对应的ThreadLocal就变成了null。而根据文档所叙述的,当key == null时,这时就能够默认该键再也不被引用,该Entry就能够被直接清除,该清除行为会在Entry自己的set()/get()/remove()中被调用,这样就能 必定状况下避免内存泄漏

这时就有一个问题出现了,做为keyThreadLocal变成了null,那么做为value的变量但是强引用呀,这不就致使内存泄漏了吗?

其实通常状况下也不会,由于即便再不济,线程在执行结束时,天然也会消除其对value的引用,使得Value可以被GC回收。

固然,在某种状况下(好比使用了 线程池),线程再次被使用,Value这时依然能够被获取到,天然也就发生了内存泄漏,所以此时,咱们仍是须要经过手动将value的值设置为null(即调用ThreadLocal.remove()方法)以规避内存泄漏的风险。

参考&感谢


关于我

Hello,我是却把清梅嗅,若是您以为文章对您有价值,欢迎 ❤️,也欢迎关注个人博客或者Github

若是您以为文章还差了那么点东西,也请经过关注督促我写出更好的文章——万一哪天我进步了呢?

相关文章
相关标签/搜索