ThreadLocal浅析

107 阅读4分钟

前言

看名字就知道,这玩意铁定跟Thread有关系,其实它相当于一个线程工具类,用于获取当前线程所绑定的一些变量。

在实际的开发中,我们也经常使用线程变量,比如我们会在鉴权拦截器中,鉴权通过后将用户ID、租户ID等信息绑定到我们的线程变量,这样我们在接口内业务开发过程中就是随时随地的取出当前访问的用户ID、租户ID信息了,非常方便。

当然,我在别的博客中看到,ThreadLocal其实是为了解决多线程下并发问题的一种处理方式。每个用户访问都是一个线程,从ThreadLocal中获取的数据和其他线程互不影响(如果纯粹是一个静态变量,那么每个线程获取到的都是相同的,这样就会存在线程安全问题),相当于一个本地副本变量。

前提知识

在了解ThreadLocal之前,我们需要先了解一下Thread相关的属性

public class Thread implements Runnable {
  // ...省略
  
  /* 与此线程有关的 ThreadLocal 值。此映射由 ThreadLocal 类维护 */
  ThreadLocal.ThreadLocalMap threadLocals = null;
  
  /* 与此线程有关的 InheritableThreadLocal 值。该属性会在new Thread()时 */
  ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
  
  // 获取当前线程
  public static native Thread currentThread();
  
  // ... 省略

}

分析

ThreadLocal<T>常用的API主要有以下几个

// 1. 获取
public T get();

// 2. 给线程变量赋值
public void set(T value);

// 3. 移除线程变量
public void remove();

API

get方法

 public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取ThreadLocalMap,其实就是获取当前线程的的threadLocals属性, 这个属性的类型ThreadLocal的一个内部类ThreadLocalMap,具体见下1
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            // ThreadLocalMap内部又有一个Entry静态类,ThreadLocalMap对象内有一个Entry[]属性,这个Entry[]初始容量为16(类似HashMap)
            // Entry的Key为当前ThreadLocal对象, 值为ThreadLocal泛型类型对象
            
            // 获取Key为当前ThreadLocal的Entry对象,具体见下3
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        // 当ThreadLocalMap或ThreadLocalMap.Entry为null,初始化一个Value, 详情见下2
        return setInitialValue();
    }

getMap方法

ThreadLocalMap getMap(Thread t) {
      // 返回线程t的threadLocals属性
      return t.threadLocals;
}

setInitialValue方法

private T setInitialValue() {
        // 初始化一个为Null的泛型对象(这个方法直接return了一个null,其他啥也没有)
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 如果当前线程的threadLocals属性不为null,则将value(其实就是null)赋值到ThreadLocalMap内的Entry[x]的value,key为当前ThreadLocal,x是根据k的hashCode算出来的
            map.set(this, value);
        else
            // map为null,则初始化一个ThreadLocalMap(包括内部的Entry[])
            createMap(t, value);
        return value;
    }

ThreadLocalMap.Entry

 static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            // Entry就是一个键值对,Key为当前ThreadLocal对象, 值为ThreadLocal泛型类型对象
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
  }

getEntry方法

private Entry getEntry(ThreadLocal<?> key) {
        // 根据hashCode计算出入参的key(ThreadLocal对象),在当前table数组的索引值
        int i = key.threadLocalHashCode & (table.length - 1);
        Entry e = table[i];
        if (e != null && e.get() == key)
            return e;
        else
            return getEntryAfterMiss(key, i, e);
    }

set方法

public void set(T value) {
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 获取当前线程的threadLocals属性(具体上面get方法中已经分析过了)
        ThreadLocalMap map = getMap(t);
        if (map != null)
            // 将value覆盖式赋值到对应的Entry的value(先通过this(当前ThreadLocal对象)的hashCode计算得到当前Key所在的table数组的索引值,取出对应的Entry键值对,再将value赋值到Entry的value)
            map.set(this, value);
        else
            // 没有则创建一个ThreadLocalMap,并将Value进行赋值
            createMap(t, value);
    }

remove方法

其实在JDK1.5之后,ThreadLocal做了很多防止内存溢出的操作,比如将ThreadLocal.ThreadLocalMap.Entry继承WeakReference,在EntryKey为null时会自动被回收,但是如果说这个线程会持续很久,那么这个对象可能一直会在累积,最终导致内存溢出,所以还是需要我们及时手动的进行remove

public void remove() {
     // 获取当前线程的threadLocals属性
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         // ThreadLocalMap的remove方法,见下
         m.remove(this);
 }
 

private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    // 计算当前Key在table的索引值
    int i = key.threadLocalHashCode & (len-1);
    
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
         
         // e.get()其实是调用Entry父类Reference的get()方法,也就是对象的reference属性
        if (e.get() == key) {
            // 匹配到对应的key后,则将其清除
            e.clear();
            expungeStaleEntry(i);
            return;
        }
    }
}