ThreadLocal

本质上,ThreadLocal是通过空间来换取时间,从而实现每个线程当中都会有一个变量的副本,这样每个线程就都会操作该副本,从而完全规避了多线程的并发问题。

Java中存在4种类型引用
1.强引用(strong) 如果一个对象被强停止引用所指向, 它不会被垃圾收集器回收
2.软引用(soft) 当内存空间明显不够的情况,GC才会将软引用所指向对象回收
3.弱引用(weak) 在下一次垃圾回收的情况下被回收
4.虚引用(phantom)

除了强引用用外, 其它要继承Reference

重要: ThreadLocal中 Entry extends WeakReference 防止内存泄露 (继承WeakReference后, 若代码写得不正确也可能千万内存泄露)

栈: 引用在此上, 局部变量引用
堆: new出来的对象

public static void main(String[] args) {
    ThreadLocal threadLocal = new ThreadLocal();
    threadLocal.set("hello");
    System.out.println(threadLocal.get()); //hello
    threadLocal.set("jimmy");
    System.out.println(threadLocal.get()); //jimmy
  }

源码

  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();
    }
static class ThreadLocalMap {
        static class Entry extends WeakReference> {
            /** The value associated with this ThreadLocal. */
            Object value;
            Entry(ThreadLocal k, Object v) {
                super(k);
                value = v;
            }
        }
        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
image.png

https://www.cnblogs.com/jiangxinlingdu/p/11055540.html

ThreadLocal是什么

ThreadLocal是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,特别适用于各个线程依赖不同的变量值完成操作的场景。

从数据结构入手

下图为ThreadLocal的内部结构图


image.png

从上面的结构图,我们已经窥见ThreadLocal的核心机制:

每个Thread线程内部都有一个Map。
Map里面存储线程本地对象(key)和线程的变量副本(value)
但是,Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。

ThreadLocalMap的问题

由于ThreadLocalMap的key是弱引用,而Value是强引用。这就导致了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

如何避免泄漏

既然Key是弱引用,那么我们要做的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就可以被回收。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

ThreadLocal threadLocal = new ThreadLocal();
try {
    threadLocal.set(new Session(1, "Misout的博客"));
    // 其它业务逻辑
} finally {
    threadLocal.remove();
}

总结

每个ThreadLocal只能保存一个变量副本,如果想要上线一个线程能够保存多个副本以上,就需要创建多个ThreadLocal。
ThreadLocal内部的ThreadLocalMap键为弱引用,会有内存泄漏的风险。
适用于无状态,副本变量独立后不影响业务逻辑的高并发场景。如果如果业务逻辑强依赖于副本变量,则不适合用ThreadLocal解决,需要另寻解决方案。

你可能感兴趣的:(ThreadLocal)