关于java:ThreadLocal的简单理解

36次阅读

共计 2745 个字符,预计需要花费 7 分钟才能阅读完成。

一、背景

最近有人问我 ThreadLocal 是如何做到在每个线程中的值都是隔离的,此处写篇文章来简略记录下。

二、ThreadLocal 解决的问题

  1. 该数据属于该线程 Thread 本身,别的线程无奈对其影响。(须要留神:须要调用 ThreadLocal 的 remove 办法)
  2. 不存在线程平安问题。(因为 ThreadLocal 类型的变量只有本身的线程能够拜访,所以这点是成立的。)

比方:

用户登录胜利后,须要将 登录用户信息 保存起来,以不便在零碎中的任何中央都能够应用到,那么此时就能够应用 ThreadLocal 来实现。例如:Spring Security中的 ThreadLocalSecurityContextHolderStrategy 类。

三、如何创立一个 ThreadLocal 实例

private static final ThreadLocal<String> USER_NAME = new ThreadLocal<>();

ThreadLocal 的实例举荐应用 private static final 来润饰。

四、ThreadLocal 如何做到线程变量隔离

1、了解 3 个类

  1. ThreadLocal: 此类提供了一个简略的 set,get,remove 办法,用于设置,获取或移除 绑定到线程本地变量中的值。
  2. ThreadLocalMap: 这是在 ThreadLocal 中定义的一个类,能够简略的将它了解成一个 Map,不过它的 key 是 WeakReference 弱援用类型,这样当这个值没有在别的中央援用时,在产生垃圾回收时,这个 map 的 key 会被主动回收,不过它的值不会被主动回收。

    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            // key 弱援用
            super(k);
            // 值强援用
            value = v;
        }
    }
  3. Thread:这个是线程类,在这个类中存在一个 threadLocals 变量,具体的类型是ThreadLocal.ThreadLocalMap

2、看下 set 办法是如何实现的

public void set(T value) {
    // 获取以后线程
    Thread t = Thread.currentThread();
    // 获取绑定到这个线程本身的 ThreadLocalMap,这个 ThreadLocalMap 是从 Thread 类的 `threadLocals` 变量中获取的
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 向 map 中设置值,key 为 ThreadLocal 对象的实例。map.set(this, value);
    } else {
        // 如果 map 不存在,则创立进去。createMap(t, value);
    }
}

通过上方的代码,咱们可知:当咱们向 ThreadLocal 中设置一个值,会通过如下几个步骤:

  1. 获取以后线程Thread
  2. 获取以后线程的 ThreadLocalMap 对象。
  3. ThreadLocalMap 中设置值,key 为 ThreadLocal 对象,值为具体的值。

3、看看 get 办法如何实现

public T get() {
    // 获取以后线程
    Thread t = Thread.currentThread();
    // 获取这个线程本身绑定的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // this 是 ThreadLocal 对象,获取 Map 中的 Entry 对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {@SuppressWarnings("unchecked")
            // 获取具体的值
            T result = (T)e.value;
            return result;
        }
    }
    // 设置初始值
    return setInitialValue();}

从上方的 get 和 set 办法能够得悉,通过往 ThreadLocal 对象中设置值或获取值,其实是最终操作到 Thread 对象中的 threadLocals 字段中,而这个字段是 Thread 本身的,因而做到了隔离。

五、ThreadLocalMap 中的 hash 抵触是如何解决的

1、ThreadLocal 对象的 hash 值是怎么的

private final int threadLocalHashCode = nextHashCode();
    // 该 ThreadLocal 对象本身的 hash code 值
    private final int threadLocalHashCode = nextHashCode();
    // 从 0 开始
    private static AtomicInteger nextHashCode = new AtomicInteger();
    // 每次递增固定的值
    private static final int HASH_INCREMENT = 0x61c88647;
    // hash code 值计算
    private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

从上方的代码中能够,ThreadLocal类在实例化进去之后,它的 hash code 值(threadLocalHashCode) 就是固定的,即便 ThreadLocal 调用了 set 办法,设置了别的值,它的 hash code 值 也不会发生变化。

此字段 threadLocalHashCodeThreadLocal对象的 hash 值,在 ThreadLocalMap 中须要用到这个 hash 值。

2、解决 hash 抵触

ThreadLocalMap解决 hash 抵触的方法很简略。就是通过线性探测法。如果产生了抵触,就去找数组前面的可用地位。具体看上图。演示的是 A 和 B 2 个 ThreadLocal 对象,而后产生了抵触,A 和 B 存在的地位在那个中央。

六、ThreadLocal 内存透露

ThreadLocal 为什么会存在内存透露呢?

这是因为 ThreadLocalMap 中的 keyWeakReference类型,也就是弱援用类型,而弱援用类型的数据在没有内部强援用类型的话,在产生 gc 的时候,会主动被回收掉。留神: 此时是 key 被回收了,然而 value 是没有回收的。因而在 ThreadLocalMap 中的 Entry[] 中可能存在 keynull,然而 value 是具体的值的对象,因而就产生了内存透露。

解决内存透露:

当咱们应用完 ThreadLocal 对象后,须要在适当的机会调用 ThreadLocal#remove() 办法。 否则就只有等 Thread 主动退出能力革除,如果是应用了线程池,Thread会重用,革除的机会就更难。

正文完
 0