ThreadLocal 原理

简介

ThreadLoal 变量,线程局部变量,同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本。这里有几点需要注意:

  • 因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
  • 既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
  • ThreadLocal 提供了线程本地的实例。它与普通变量的区别在于,每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

总的来说,ThreadLocal 适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。

使用

private static final ThreadLocal<T> threadLocal = new ThreadLocal<T>();
threadLocal.set();
threadLocal.get();
threadLocal.remove();

原理

类图

ThreadLocal.png
//ThreadLocal是个泛型类,保证可以接受任何类型的对象
public class ThreadLocal<T> {
    ...
    static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
       ...
    }
    //ThreadLocalMap 用来存放数据
    static class ThreadLocalMap {
         static class Entry extends WeakReference<ThreadLocal<?>> {
              ...
          }
    }
    ...
}

通过代码理解原理

ThreadLocal提供了线程内存储变量的能力,这些变量在每个线程之间是相互独立的。我们可以通过get和set方法得到或设置当前线程对应的值。

设置数据 set()
public void set(T value) {
    //获取当前线程
    Thread t = Thread.currentThread();
    //获取数据实际的存储结构
    ThreadLocalMap map = getMap(t);
    //如果map为空
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    //t为当前线程,在当前线程中维护了一个ThreadLocalMap
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    //将新创建的ThreadLocalMap赋值给Thread中的threadLocals变量
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
获取数据 get()
public T get() {
     //获取当前线程
    Thread t = Thread.currentThread();
    //获取数据实际的存储结构
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        //以当前ThreadLocal对象为key获取对应存储的value
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

private T setInitialValue() {
    //如果还没有初始化ThreadLocalMap,则会创建并将初始化的value值设置到ThreadLocalMap中
    T value = initialValue();//初始化的value为 null
    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;
}
remove()
 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

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;
        }
    }
}
ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类,其存储结构使用的是Entry,实际为一个WeakReference弱引用,使用弱引用的原因为当ThreadLocal没有强引用的情况下,在垃圾回收的时候key可以及时的被清理掉。但是请注意value还是强引用,不会被清理掉,所以会出现key为null的value,所以在不在使用的时候及时调用remove方法清除。

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

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

   ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    ...
}

内存泄露问题

通过上面的一些介绍可以知道,ThreadLocalMap中维护了一个Entry的数据结构,其中key使用了弱引用来保证GC时回收,但是value部分仍然存在强引用无法回收导致泄露。


内存泄露

使用场景

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。(Spring声明式事务)
4、数据库连接,Session会话管理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容