美文网首页
ThreadLocal

ThreadLocal

作者: 龙剑灵 | 来源:发表于2020-06-08 14:59 被阅读0次

    本质上,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<String> 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<ThreadLocal<?>> {
                /** 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<Session> threadLocal = new ThreadLocal<Session>();
    try {
        threadLocal.set(new Session(1, "Misout的博客"));
        // 其它业务逻辑
    } finally {
        threadLocal.remove();
    }
    

    总结

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

    相关文章

      网友评论

          本文标题:ThreadLocal

          本文链接:https://www.haomeiwen.com/subject/reamohtx.html