ThreadLocal技术分享

结构

ThreadLocal 的实现是这样的:每个 Thread 维护⼀个 ThreadLocalMap 映射表,这个映射表的key 是 ThreadLocal 实例本身, value 是真正需要存储的 Object 。也就是说 ThreadLocal 本身并不存储值,它只是作为⼀个 key 来让线程从 ThreadLocalMap 获取value 。

在这里插入图片描述

源码分析

set方法

源码:

/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null)
    map.set(this, value);
  else
    createMap(t, value);
}

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

简单分析:

    1. 根据当前线程获取到ThreadLocalMap(如果ThreadLocalMap不存在,就创建⼀个实例)
    2. 以当前threadlocal对象为key保存到ThreadLocalMap中

get方法

/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if(map != null) {
      ThreadLocalMap.Entry e = map.getEntry(this);
      if (e != null)
        return (T)e.value;
    }
    returnsetInitialValue();
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; 
}
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; 
}

protected T initialValue() {
    return null; 
}

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

简单分析:

    1. 根据当前线程获取到ThreadLocalMap(如果ThreadLocalMap不存在,就创建⼀个实例)
    2. 从ThreadLocalMap中获取当前threadlocal对象对应的value

remove方法

/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* <tt>initialValue</tt> method in the current thread.
*
* @since 1.5
*/
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

简单分析:

  1. 根据当前线程获取到 ThreadLocalMap,如果 ThreadLocalMap 不存在则认为已经移除,或者本就不存在

  2. 从 ThreadLocalMap 中删除当前 threadlocal 对象

threadlocalMap

ThreadLocalMap是ThreadLocal的内部类,没有实现Map接⼝,⽤独⽴的⽅式实现了Map的功能,其内部的Entry也独⽴实现。

如图:

在这里插入图片描述

在ThreadLocalMap中,也是⽤Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,这点在Entry的构造⽅法已经明确做了限制。

static class Entry extends WeakReference<ThreadLocal> {
 /** The value associated with this ThreadLocal. */
   Object value;
   Entry(ThreadLocal k, Object v) {
       super(k);
       value = v;
   }
}

如上述代码所示:

Entry继承⾃WeakReference(弱引⽤,⽣命周期只能存活到下次GC前),但只有Key是弱引⽤类型的,Value并⾮弱引⽤。

hash冲突怎么解决

ThreadLocalMap 中解决 Hash 冲突的⽅式是采⽤线性探测的⽅式,所谓线性探测,就是根据初始 key 的 hashcode 值确定元素在 table 数组中的位置,如果发现这个位置上已经有其他 key 值的元素被占⽤,则利⽤固定的算法寻找⼀定步⻓的下个位置,依次判断,直⾄找到能够存放的位置。
ThreadLocalMap解决Hash冲突的⽅式就是简单的步⻓加1或减1,寻找下⼀个相邻的位置。

private void set(ThreadLocal<?> key, Object value) {
  Entry[] tab = table;
  int len = tab.length; 
  int i = key.threadLocalHashCode & (len-1);
  for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {
      ThreadLocal<?> k = e.get();
      if (k == key) {
        e.value = value;
        return;
      }
      if (k == null) {
          replaceStaleEntry(key, value, i);
          return;
      }
  }
  tab[i] = new Entry(key, value);
  int sz = ++size;
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
      rehash();
}

private static int nextIndex(int i, int len) {
  return ((i + 1 < len) ? i + 1 : 0);
}

显然 ThreadLocalMap 采⽤线性探测的⽅式解决Hash冲突的效率很低,如果有⼤量不同的 ThreadLocal 对象放⼊ map 中时发送冲突,或者发⽣⼆次冲突,则效率很低。

为什么使用弱引用

分两种情况讨论:

key 使⽤强引⽤:引⽤的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引⽤,如果没有⼿动删除ThreadLocal 不会被回收,导致 Entry 内存泄
漏。
key 使⽤弱引⽤:引⽤的 ThreadLocal 的对象被回收了,由于ThreadLocalMap 持有ThreadLocal 的弱引⽤,即使没有⼿动删除,ThreadLocal 也会被回收。value 在下⼀次 ThreadLocalMap 调⽤ set , get , remove 的时候会被清除。

⽐较两种情况,我们可以发现:由于 ThreadLocalMap 的⽣命周期跟 Thread ⼀样⻓,如果都没有⼿动删除对应 key ,都会导致内存泄漏,但是使⽤弱引⽤可以多⼀层保障:弱引⽤ ThreadLocal 不会内存泄漏,对应的value在下⼀次ThreadLocalMap调⽤set,get,remove的时候会被清除。

  1. get ⽅法
    在 get 的过程中,如果遇到某槽位满⾜⼀下条件: table[i]!=null && table[i].get()==null ,就清理该槽位 expungeStaleEntry(i)
private Entry getEntry(ThreadLocal<?> key) {
    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);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length;
    while (e != null) {
      ThreadLocal<?> k = e.get();
      if (k == key)
      return e;
      if (k == null)
        expungeStaleEntry(i);
      else
        i = nextIndex(i, len);
      e = tab[i];
    }
    return null; 
}
 
 /**
* Expunge a stale entry by rehashing any possibly colliding entries
* lying between staleSlot and the next null slot. This also expunges
* any other stale entries encountered before the trailing null.
* 清理指定的槽位 + rehash
**/
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // expunge entry at staleSlot
    tab[staleSlot].value= null;
    tab[staleSlot] = null;
    size--;
    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
              e.value= null;
              tab[i] = null;
              size--;
            } else {
              int h = k.threadLocalHashCode & (len - 1);
              if (h != i) {
                tab[i] = null;
                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                h = nextIndex(h, len);
                tab[h] = e;
              }
          }
    }
    return i; 
}
 
  1. remove⽅法
    在remove的过程中,如果遇到某槽位满⾜⼀下条件: table[i]!=null &&
    table[i].get()==null ,就清理该槽位 expungeStaleEntry(i)
private void remove(ThreadLocal<?> key) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len - 1);
    for (Entry e = tab[i];
          e != null;
          e = tab[i = nextIndex(i, len)]) {
          if (e.get() == key) {
            e.clear();
            expungeStaleEntry(i);
            return;
           }
     }
 }
  1. set⽅法

如果遇到某槽位满⾜⼀下条件: table[i]!=null && table[i].get()==null ,就替换掉该槽位的值 replaceStaleEntry(i)

在找到⼀个空的槽位,把(key,value)放在该槽位里面后,再触发 cleanSomeSlots

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
      i = nextIndex(i, len);
      Entry e = tab[i];
      if (e != null && e.get() == null) {
        n = len;
        removed = true;
        i = expungeStaleEntry(i);
      }
    } while( (n >>>= 1) != 0);
    return removed; 
}

因此, ThreadLocal 内存泄漏的根源是:由于 ThreadLocalMap 的⽣命周期跟 Thread ⼀样⻓,如果没有⼿动删除对应 key 就会导致内存泄漏,⽽不是因为弱引⽤。

InheritableThreadLocal

源码

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
    * Computes the child's initial value for this inheritable threadlocal
    * variable as a function of the parent's value at the time the child
    * thread is created. This method is called from within the parent
    * thread before the child is started.
    */
    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对于的ThreadLocalMap是放在 thread.inheritableThreadLocals 属性中的,与ThreadLocal不同。

拷贝的实现原理

ThreadLocal 的拷⻉发⽣在:当前线程⽣成⼦线程实例的时候。如果当前线程的 inheritableThreadLocals 属性不为空,就会把该属性拷⻉到⼦线程的inheritableThreadLocals 属性中

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name, long
stackSize) {
    init(g, target, name, stackSize, null, true);
}
private void init(....) {
    ......
    Thread parent = currentThread();
    ......
    if (inheritThreadLocals && parent.inheritableThreadLocals != null)    
    this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
    ......
}


static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
}
private ThreadLocalMap(ThreadLocalMap parentMap) {
    Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = newEntry[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) {
              Object value = key.childValue(e.value);
              // 注意:key和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++;
           }
        }
    }
}

TransmittableThreadLocal

概述

JDK 的 InheritableThreadLocal 类可以完成⽗线程到子线程的值传递。但对于使⽤线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使⽤的;这是父子线程关系的ThreadLocal 值传递已经没有意义,应⽤需要的实际上是把任务提交给线程池时的 ThreadLocal 值传递到任务执⾏时。

TransmittableThreadLocal 类继承并加强 InheritableThreadLocal 类,解决上述的问题。

相比 InheritableThreadLocal ,添加了

    1. protected ⽅法 copy ⽤于定制任务提交给线程池时的 ThreadLocal 值传递到任务执行时的拷贝⾏为,缺省传递的是引⽤。
    2. protected ⽅法 beforeExecute / afterExecute 执⾏任务( Runnable / Callable )的前/后的生命周期回调,缺省是空操作。

完整时序图:

图片

拷贝的实现原理

在这里插入图片描述

TransmittableThreadLocal 覆盖实现了 ThreadLocal 的 set、get、remove,实际存储 ThreadLocal 值的工作还是 ThreadLocal 父类完成,TransmittableThreadLocal 只是为每个使⽤它的 Thread 单独记录⼀
份存储了哪些 TransmittableThreadLocal 对象。拿 set 来说就是这个样子:

public final void set(T value) {
    super.set(value);
    if (null == value) removeValue();
    else addValue();
}
private void removeValue() {
    holder.get().remove(this);
}
private void addValue() {
    if (!holder.get().containsKey(this)) {
      holder.get().put(this, null); // WeakHashMap supports null value.
    }
}
private static InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>> holder = new InheritableThreadLocal<Map<TransmittableThreadLocal<?>, ?>>() {
      @Override
      protected Map<TransmittableThreadLocal<?>, ?> initialValue() {
         return new WeakHashMap<TransmittableThreadLocal<?>, Object>();
      }
     
      @Override
      protected Map<TransmittableThreadLocal<?>, ?> childValue(Map<TransmittableThreadLocal<?>, ?> parentValue) {
         return new WeakHashMap<TransmittableThreadLocal<?>, Object> (parentValue);
      }
};

TtlRunnable在装饰Runnable的的时候将TransmittableThreadLocal.holder中的threadlocal以及其对于的value拷贝到 capturedRef 属性中,在子线程调⽤run方法的时候,把 capturedRef 复制到子线程中的threadlocal中。


public final class TtlRunnable implements Runnable, TtlEnhanced {
    private final AtomicReference<Object> capturedRef;
    private final Runnable runnable;
    ......
    @Override
    public void run() {
        Object captured = capturedRef.get();
        if (captured == null || releaseTtlValueReferenceAfterRun &&
          !capturedRef.compareAndSet(captured, null)) {
          throw new IllegalStateException("TTL value reference is released after run!");
        }
        Object backup = replay(captured);
        try {
          runnable.run();
        } finally {
          restore(backup);
        }
     }
   .....
}

如果觉得本文有帮助可以分享给自己的小伙伴们!邀请他们关注下我的微信公众号

小哥爱code

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值