内卷老员工之threadLocal

149 阅读1分钟

这是我参与8月更文挑战的第6天,活动详情查看:8月更文挑战

ThreadLocal

  • java ThreadLocal类相当于提供了一种线程安全的简单方法,以此创建的内容只能在线程中存储和读取,其他线程无法访问到当前线程的变量。

threadLocal与synchronized的对比

  • Synchronized是通过线程等待,通过加锁,用更长的时间换取变量共享,解决冲突。
  • ThreadLocal是通过每个线程单独一份存储空间,用更大的空间换取来解决冲突。相比于synchronized,threadLocal的变量之间不存在共享的可能性,每个变量都是单独空间内隔离的内容,与synchronized的共享实现方式有所不同。
  • 正是由于这种隔离的特殊性,threadLocal在一些场景下具有更好的使用效果。

threadLocal的一些api应用

创建threadLocal对象

private static ThreadLocal<T> tl1 = new ThreadLocal<>();
  • T为所要存储的变量类型

给threadLocal变量赋值

tl1.set("th1===test");

获取threadLocal变量值

tl1.get()

移除threadLocal变量存储的值

tl1.remove()

threadLocal的底层存取原理

  • 当调用threadLocal.set(value)方法时,通过阅读源码可以知道,将在底层调用线程的threadLocalMap对象。以当前的threadLocal对象的弱引用作为key值,以传入的值作为value值,存储到threadLocalMap对象中。
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}
  • 而当读取时,直接调用threadLocal.get()方法,利用当前的threadLocal对象,到threadLocalMap中读取相应的值。
public T get() {
    Thread t = Thread.currentThread();
    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;
        }
    }
    return setInitialValue();
}

threadLocal出现oom的原理及如何避免

  • threadLocal的回收原理主要是利用弱引用。对于同一个变量同时进行强引用和弱引用。当threadLocal对象调用结束,被垃圾回收强引用结束时,弱引用的threadLocalMap对应key会在下一次gc时被垃圾回收。这种情况下会使得threadLocalMap存在key为null,value为实际存储值,且永远不会被回收导致内存泄漏或oom。

  • 实际上,当线程结束时或调用set(value)get()方法时,会自动调用remove()方法,便可自动避免内存的泄露。

  • 因此实际上出现内存泄露的情况主要是使用线程池调用threadLocal,而线程使用结束key被置为空,同时不会被再次调用set(value)get()方法。因此可以轻易推断出,想要避免threadLocal的内存泄露问题,只需要在使用结束时手动调用remove()方法,即可将value的引用释放,从根本上解决内存泄露的问题。

InheritableThreadLocal

  • 该类继承于ThreadLocal类,与其每个线程拥有自己的threadLocal变量不同,该类允许当前线程于线程的子线程共同有权限访问InheritableThreadLocal变量。
InheritableThreadLocal<String> t = new InheritableThreadLocal();
Thread t1 = new Thread(() -> {
    t.set("test");
    Thread t2 = new Thread(() -> {
        String value = t.get();
    });
});

Thread t3 = new Thread(() -> {
    t.get();
});
  • 线程t2为线程t1的子线程,因此可以直接读取t1线程所设置的t的值。而在t3线程中,由于于t1线程没有子线程的关系,因此无法读取t1线程中设置的t的值,所读取的值为null。

后记

  • 千古兴亡多少事?悠悠。不尽长江滚滚流。