ThreadLocal

ThreadLocal

​ ThreadLocal 是 java提供的一种线程内部数据存储方案,通过ThreadLocal实例提供的get()/set(T)方法可以操作Thead类中的TheadLocalMap实例.从而在Thread内部存储线程私有的数据.

​ Thread类内部定义了两个 ThreadLocal.ThreadLocalMap类变量,来保存线程数据

public
class Thread implements Runnable {
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

TheadLocalMap是ThreadLocal中定义的一个静态类,结构基本和HashMap类似,不同的是ThreadLocalMap处理hash冲突的方式用的是线性再探的方式

private Entry[] table;

static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

TheadLocalMap有几个需要注意的点:

  1. 和HashMap不同,TheadLocalMap和是使用线性再探测方法来解决hash冲突,冲突后往后寻找空余的位置存储.

  2. 这里entry的key是当前的threadLocal实例,value是我们需要存储的私有化数据

  3. 注意,entry对threadLoca对象的引用是弱引用,这是因为当一个ThreadLocal没有被其他地方引用后,虚拟机可以回收这个ThreadLocal实例的内存,如果这里不是弱引用的话,当一个ThreadLocal实例没有被其他地方引用后,因为TheadLocalMap还持有着这个TheadLocal实例的强引用,所以这个实例一直不会被回收,会导致内存泄露.

    为什么Entry对ThreadLocal对象的引用需要时弱引用?
    
    	ThreadLocal tl = new ThreadLocal();
    	tl.set(obj);
       
    因为TheadLocalMap中key存的是ThreadLocal对象,所以在上面的操作完成后,有两个地方对tl对象进行了引用,一个是tl对象本身,它对这个堆中的tl对象进行了强引用,另一个是TheadLocalMap中的Entry对象弱引用了tl对象.
    在我们需要清理这个线程属性obj的时候,对tl对象赋NULL值
    	
    	tl = null;
    	
    这时tl释放了对tl对象的强引用,按理我们期望GC能够把堆中tl对象给清理掉,但是这时Entry对象还引用着tl对象,如果这个引用也是强引用的话,只要线程不结束,这个tl对象就会一直不被释放,最终可能会导致oom.
    
    这时还有一个问题是,Entry还持有着obj对象的引用,这个引用是强引用(因为其他引用就可能会把obj给gc掉,所以这里必须是强引用),在把tl =null 后,只要线程不结束,obj对象也一直不会被gc,所以在使用ThreadLocal时,一定要用完后remove掉.
    	tl.remove();
    	
    ThreadLocal中的set/get/resize方法,在执行的过程中,也会判断是不是有key为null的数据,有的话会删除掉,这样可以一定程度降低内存泄露的概率,但是最好还是用完把当前的threadLocal给remove掉.彻底避免内存泄漏
    
    
    线程池中的线程也尽量不要用ThreadLocal, 一是有可能内存泄露,二是由于线程复用可能会导致读到脏数据
    ps: 
    强引用: 对象不能被gc
    软引用: 空间不足时会被gc
    弱引用: 不影响gc
    虚引用: 也不影响gc, 主要用于跟踪gc过程.
    

ThreadLocal常用的方法get()/set()/remove()源码

  public T get() {
        Thread t = Thread.currentThread();
        //InheritableThreadLocal重写了个getMap,所以对于InheritableThreadLocal来说,这里拿到的就是inheritableThreadLocals
        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();
    }
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
         	//调用ThreadLocalMap中的remove()方法
             m.remove(this);
     }
 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            //1. 获取map中key对应的Entry
            for (Entry e = tab[i];
            	//只要 e!=null,说明没找到对应的Entry,就一直往后循环,这是因为用的是线性再探的方法处理冲突
                 e != null;
                 //e = 下一个Entry
                 e = tab[i = nextIndex(i, len)]) {
                 //找到key对应的Entry e
                if (e.get() == key) {
                	//把e = null ,把e在堆中的Entry对象释放掉,这样堆里的key,和value都可以被回收了
                    e.clear();
                    //重新hash i后的元素,并且这个过程还会检查key为null的Entry对象并置为null,避免内存泄露
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

InheritableThreadLocal

  • InheritableThreadLocal继承自ThreadLocal,重写了getMap()/createMap()/childValue()方法,重写的目的是为了能够操作Thread中的inheritableThreadLocals属性,这里存放了父线程中的TheadLocalMap数据.
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

  • 在Thread中的初始化方法init()中,会将父线程中的TheadLocalMap拷贝到inheritableThreadLocals中.
 private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
          ......
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
          ......
  • 使用InheritableThreadLocal便能操作Thread中的inheritableThreadLocals属性,获取父类的ThreadLocal属性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值