• ThreadLocal的原理、作用、使用弱引用原因、应用举例


    一. 原理

      ThreadLocal就是一个类,他有get、set方法,可以起到一个保存、获取某个值的作用。但是这个类的get、set方法有点特殊,各个线程调用时是互不干扰的,就好像线程在操作ThreadLocal对象时是在操作线程自己的私有属性一样。具体原因在于他的方法实现:

    public T get() {
            Thread t = Thread.currentThread();  //先确定调用我的线程
            ThreadLocalMap map = getMap(t);  //根据调用我的线程,找到这个线程的ThreadLocalMap对象
            if (map != null) {
                ThreadLocalMap.Entry e = map.getEntry(this);  //以ThreadLocal对象为key,找到对应元素
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    T result = (T)e.value;   //讲元素的value返回
                    return result;
                }
            }
            return setInitialValue();  //如果调用我的线程没有ThreadLocalMap对象,则返回初始值
        }
    public void set(T value) {
            Thread t = Thread.currentThread();  //先确定调用我的是哪个线程
            ThreadLocalMap map = getMap(t);  //获取调用我的线程的ThreadLocalMap 
            if (map != null)
                map.set(this, value);  //如果那个线程有map,就将此ThreadLocal对象为key的value设置好
            else
                createMap(t, value);   //如果那个线程还没有map,先创建一个再设置
        }

    ThreadLocalMap是ThreadLocal的内部类,为了不造成混乱,可以把他看作一个普通的类。ThreadLocalMap其实类似与HashMap,也是通过key获取某个值(key就是ThreadLocal对象),也是数组存储键值对,拉链法解决冲突等。一个Thread类持有一个ThreadLocalMap实例。

    通过上面的源码也可以看出:线程互不干扰的操作ThreadLocal的原因就是,它的set、get方法是要先获取当前线程,然后修改、操作这个线程对象的成员属性。也就是说,调用ThreadLocal对象的set、get方法实际上是在操作当前线程的成员属性,只不过这些属性是通过ThreadLocal对象为key找到的而已。为了值观明了,看下图:

    简单概括过程:有ThreadLocal对象 tl,线程 t 调用 tl.get(), 则去线程 t 的ThreadLocalMap属性对象里找到一个entry,若entry.key == tl返回true,则此entry是目标entry,此entry.value就是我们的目标。

    ThreadLocal对象只是一个获取当前线程某个私有属性的渠道而已,提供了set、get的入口,同时作为key去获取和设置目标值。真正的有效目标是属于线程对象私自持有的,自然通过ThreadLocal对象获取的值也就不会受其他线程影响啦。

    二. 用法示例

    public class ThreadLocalTest {
     
        private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>();  //private是为了安全,是一个普遍做法;static是因为这个变量有可能在static方法中使用
     
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                tl.set(1);
                tl.get();
                .........
            });
        }
    }

    理解了ThreadLocal的原理,使用起来很简单,注意ThreadLocal对象的定义位置,检查作用域,保证可以被要使用它的线程访问到。

    三. 关于Entry的弱类型引用

      如果阅读ThreadLocalMap的Entry源码会发现,Entry的key是弱引用:

    static class Entry extends WeakReference<ThreadLocal<?>> {
                /** The value associated with this ThreadLocal. */
                Object value;
    
                Entry(ThreadLocal<?> k, Object v) {
                    super(k);  //由于Entry继承了WeakReference,所以这里以一个弱引用指向ThreadLcoal对象
                    value = v;
                }
            }

    为什么要这么做呢?看下面的这种场景:

    public void func1() {
            ThreadLocal tl = new ThreadLocal<Integer>(); //line1
             tl.set(100);   //line2
             tl.get();       //line3
    }

    line1新建了一个ThreadLocal对象,t1 是强引用指向这个对象;line2调用set()后,新建一个Entry,通过源码可知entry对象里的 k是弱引用指向这个对象。如图:

    当func1方法执行完毕后,栈帧销毁,强引用 tl 也就没有了,但此时线程的ThreadLocalMap里某个entry的 k 引用还指向这个对象。若这个k 引用是强引用,就会导致k指向的ThreadLocal对象及v指向的对象不能被gc回收,造成内存泄漏,但是弱引用就不会有这个问题(弱引用及强引用等这里不说了)。使用弱引用,就可以使ThreadLocal对象在方法执行完毕后顺利被回收,而且在entry的k引用为null后,再调用get,set或remove方法时,就会尝试删除key为null的entry,可以释放value对象所占用的内存。

    概括说就是:在方法中新建一个ThreadLocal对象,就有一个强引用指向它,在调用set()后,线程的ThreadLocalMap对象里的Entry对象又有一个引用 k 指向它。如果后面这个引用 k 是强引用就会使方法执行完,栈帧中的强引用销毁了,对象还不能回收,造成严重的内存泄露。

    注意:虽然弱引用,保证了k指向的ThreadLocal对象能被及时回收,但是v指向的value对象是需要ThreadLocalMap调用get、set时发现k为null时才会去回收整个entry、value,因此弱引用不能保证内存完全不泄露。我们要在不使用某个ThreadLocal对象后,手动调用remoev方法来删除它,尤其是在线程池中,不仅仅是内存泄露的问题,因为线程池中的线程是重复使用的,意味着这个线程的ThreadLocalMap对象也是重复使用的,如果我们不手动调用remove方法,那么后面的线程就有可能获取到上个线程遗留下来的value值,造成bug。

    四. 用途举例

      从ThreadLocal类的特性就知道它的用途了,它可以看成专属于线程的变量(实际上是通过它找到线程自己的某个Entry属性对象),不受其他线程干扰,记录着线程的某些信息。作用域比较特殊,它跟随线程的一生,无论线程执行到哪个类的哪个方法,我都随时可以用get()方法拿出来用。比如:在web后台中,可以将http请求的信息包装到ThreadLocal对象 假设为tl,执行此请求的线程在开始前执行 tl.set(httpRequest),那么这个处理请求的线程无论执行到哪,都可以由tl.get()获取当前的请求信息。

      Spring的RequestContextHolder就是这么操作的,这样在使用切面时,也可以获取到请求信息了(切面编程时自身是只可以获取到方法名+方法参数信息的):

  • 相关阅读:
    VK Cup 2012 Qualification Round 1 C. Cd and pwd commands 模拟
    VK Cup 2015
    DP总结 ——QPH
    Codeforces Round #244 (Div. 2) B. Prison Transfer 线段树rmq
    Codeforces Round #311 (Div. 2) E. Ann and Half-Palindrome 字典树/半回文串
    Codeforces Round #311 (Div. 2) D. Vitaly and Cycle 图论
    Codeforces Round #311 (Div. 2) C. Arthur and Table Multiset
    Codeforces Round #311 (Div. 2)B. Pasha and Tea 水题
    Codeforces Round #311 (Div. 2) A. Ilya and Diplomas 水题
    Codeforces Round #260 (Div. 1) D. Serega and Fun 分块
  • 原文地址:https://www.cnblogs.com/shen-qian/p/12108655.html
Copyright © 2020-2023  润新知