zoukankan      html  css  js  c++  java
  • Java学习2 (ThreadLocal)

    ThreadLocal

    每个线程的变量副本是存储在哪里的

    • ThreadLocal(线程局部变量)
    • 在线程之间共享变量是存在风险的,有时可能要避免共享变量,使用ThreadLocal辅助类为各个线程提供各自的实例。
    • 如果加锁,那么会开销很大

    实现原理:

    • ThreadLocal的get方法就是从当前线程的ThreadLocalMap中取出当前线程对应的变量的副本。该Map的key是ThreadLocal对象,value是当前线程对应的变量。
    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();
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    • 变量是保存在线程中的,而不是保存在ThreadLocal变量中
    • 每个线程都有一个这样的名为threadLocals 的ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型作为key和value。
    • 这样,我们所使用的ThreadLocal变量的实际数据,通过get方法取值的时候,就是通过取出Thread中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据。现在,变量的副本从哪里取出来的(本文章提出的第一个问题)已经确认解决了。
    • 每个线程内部都会维护一个类似 HashMap 的对象,称为 ThreadLocalMap,里边会包含若干了 Entry(K-V 键值对),相应的线程被称为这些 Entry 的属主线程;
    • Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用即是:为其属主线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
    • Entry 对 Key 的引用是弱引用;Entry 对 Value 的引用是强引用。

    为什么ThreadLocalMap的Key是弱引用

    • 如果是强引用,ThreadLocal将无法被释放内存。
    • 因为如果这里使用普通的key-value形式来定义存储结构,实质上就会造成节点的生命周期与线程强绑定,只要线程没有销毁,那么节点在GC分析中一直处于可达状态,没办法被回收,而程序本身也无法判断是否可以清理节点。弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

    ThreadLocalMap是何时初始化的(setInitialValue)

    在get时最后一行调用了setInitialValue,它又调用了我们自己重写的initialValue方法获得要线程局部变量对象。ThreadLocalMap没有被初始化的话,便初始化,并设置firstKey和firstValue;如果已经被初始化,那么将key和value放入map。

    ThreadLocalMap 原理

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    • 它也是一个类似HashMap的数据结构,但是并没实现Map接口
    • 也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
    • ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。所以如果存在hash冲突则使用的是开放寻址法。
    • 在ThreadLocalMap中,形如key.threadLocalHashCode & (table.length - 1)(其中key为一个ThreadLocal实例)这样的代码片段实质上就是在求一个ThreadLocal实例的哈希值,只是在源码实现中没有将其抽为一个公用函数。
      对于& (INITIAL_CAPACITY - 1),相对于2的幂作为模数取模,可以用&(2n-1)来替代%2n,位运算比取模效率高很多。至于为什么,因为对2^n取模,只要不是低n位对结果的贡献显然都是0,会影响结果的只能是低n位。
    private void setThreshold(int len) {
        threshold = len * 2 / 3;
    }
    

    内存泄露

    • 只有调用TheadLocal的remove或者get、set时才会采取措施去清理被回收的ThreadLocal对应的value(但也未必会清理所有的需要被回收的value)。假如一个局部的ThreadLocal不再需要,如果没有去调用remove方法清除,那么有可能会发生内存泄露。
    • 既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。
    • 如果使用ThreadLocal的set方法之后,没有显式的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法
    • JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。
  • 相关阅读:
    厦门游记
    2021春节时光
    2021春节一帖
    阅读清单-2021
    LSTM
    三种梯度下降算法的区别(BGD, SGD, MBGD)
    数据降维:主成分分析法
    Windows10安装Oracle 11g
    C++智能指针
    C++开发岗基础面试题
  • 原文地址:https://www.cnblogs.com/yankang/p/13109803.html
Copyright © 2011-2022 走看看