Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ThreadLocal原理分析 #22

Open
qingmei2 opened this issue Jul 30, 2019 · 0 comments
Open

ThreadLocal原理分析 #22

qingmei2 opened this issue Jul 30, 2019 · 0 comments

Comments

@qingmei2
Copy link
Owner

ThreadLocal原理分析

接下来笔者的文章方向偏向于 Android & Java 面试相关知识点系统性的总结,欢迎关注。

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

通过思维导图对其进行简单的总结:

一.ThreadLocal源码分析

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

  • 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()中取值:

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()方法也非常简单:

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之间的关系。

二、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()同理。

三、内存泄漏的风险?

在上一小节中,我们看到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

如果您觉得文章还差了那么点东西,也请通过关注督促我写出更好的文章——万一哪天我进步了呢?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant