源码分析:ThreadLocal

关键方法

set()方法

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
    	// 获取当前线程
        Thread t = Thread.currentThread();
        // 获取线程中的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断ThreadLocalMap对象是否为null,为null则去创建ThreadLocalMap对象
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

set的方法的源码比较简单易读,里面的关键部分就是去获取线程的ThreadLocalMap对象。ThreadLocalMap存在,就直接将值set到ThreadLocalMap中,并且将ThreadLocal对象做为key值。反之会去调用createMap方法创建ThreadLocalMap。

createMap()

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

这个方法就比较简单,在ThreadLocal中,定义了一个ThreadLocalMap的静态类,这里直接创建这个静态类的对象,并且将线程中的ThreadLocalMap成员变量的引用指向它。

getMap()

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

getMap就更加显而易见啦,就是直接返回线程中的ThreadLocalMap对象咯。

map.set()

/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.
			// 获取ThreadLocalMap内部的数组,ThreadLocalMap基于数组实现的
            Entry[] tab = table;
            // 获取数组长度
            int len = tab.length;
            // 根据key值的hashcode,计算index值
            int i = key.threadLocalHashCode & (len-1);
			// 这里通过循环的方式去获取数组上面的元素
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                 // 获取key值,这个key值,就是threadlocal对象咯
                ThreadLocal<?> k = e.get();
				// 如果key值相等,直接替换对象e里面的value值
                if (k == key) {
                    e.value = value;
                    return;
                }
				// 如果key为null,但是这个位置存在一个entry对象,则调用replaceStaleEntry函数去处理
                if (k == null) {
                    // 这个函数在下面有详细的步骤分解
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
			// 当key值不存在ThreadLocalMap里面,就创建一个Entry对象,并放入到数组中
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                // rehash方法的作用是去清空数组中,key值为空的entry对象
                rehash();
        }

 /**
         * Replace a stale entry encountered during a set operation
         * with an entry for the specified key.  The value passed in
         * the value parameter is stored in the entry, whether or not
         * an entry already exists for the specified key.
         *
         * As a side effect, this method expunges all stale entries in the
         * "run" containing the stale entry.  (A run is a sequence of entries
         * between two null slots.)
         *
         * @param  key the key
         * @param  value the value to be associated with key
         * @param  staleSlot index of the first stale entry encountered while
         *         searching for key.
         */
        private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                                       int staleSlot) {
            Entry[] tab = table;
            int len = tab.length;
            Entry e;

            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            // staleSlot是当前新计算出来的,需要插入数据的数组位置
            int slotToExpunge = staleSlot;
            // 这里的for循环,是根据当前的staleSlot值,向前去寻找key值为null的entry对象,并且用slotToExpunge记录数组的下标位置
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;

            // Find either the key or trailing null slot of run, whichever
            // occurs first
            // 这里的for循环,是根据当前staleSlot值,向后去寻找数组中的entry对象
            for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
                // 如果entry对象的key值不为null
                if (k == key) {
                    // 将entry对象的值替换成新值
                    e.value = value;
					// 这个数组位置给置为空
                    tab[i] = tab[staleSlot];
                    // 将entry值放入到数组的新的位置中
                    tab[staleSlot] = e;

                    // Start expunge at preceding stale entry if it exists
                    if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
                    //这个函数是用于清空数组中key值为null的entry对象的,防止这些对象造成内存泄露
                    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }

                // If we didn't find stale entry on backward scan, the
                // first stale entry seen while scanning for key is the
                // first still present in the run.
                if (k == null && slotToExpunge == staleSlot)
                    slotToExpunge = i;
            }

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
                cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

关键点来啦,在ThreadLocalMap内部,是维护了一个数组,而数组中,放的是一个Entry对象,下面接着看Entry对象的定义。

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
         // 这里的entry类,继承了弱引用
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

Entry类继承了弱引用,在构造方法中,ThreadLocal对象在这里,是用弱引用指向的,所在在ThreadLocalMap中,key值是存的是ThreadLocal对象的弱引用,而不是强引用哦,这里ThreadLocal就巧妙的使用了弱引用去解决了Map中key值内存泄露问题。(若知后续如何,可以直接拖到内存泄露剖析

get()方法

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    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();
    }

get方法的代码逻辑也不复杂,主要是得到线程map,调用map的getEntry()方法获取当前entry对象,然后从entry对象中取出value值。

remove()方法

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }
     /**
      * Remove the entry for key.
      */
     private void remove(ThreadLocal<?> key) {
         Entry[] tab = table;
         int len = tab.length;
         // 计算key在数组中的index值
         int i = key.threadLocalHashCode & (len-1);
         // 这里通过循环调用expungeStaleEntry函数,去清理数组中的entry
         for (Entry e = tab[i];
              e != null;
              e = tab[i = nextIndex(i, len)]) {
             if (e.get() == key) {
                 e.clear();
                 // 这个函数,是用于清理数据组的entry对象的。具体源码的话,这里就不详细说明了
                 expungeStaleEntry(i);
                 return;
             }
         }
     }

内存泄露剖析

在前面提到了,ThreadLocal使用了弱引用的方式来解决key值内存泄露的问题,在这里,就简单的说一下我对ThreadLocalkey泄露的问题的理解。
图1
如上图所示,展示了ThreadLocal在内存中的引用关系图。图中实线表示强引用,虚线表示弱引用。
1、普通线程使用ThreadLocal
普通线程使用完ThreadLocal,断开ThreadLocal的强引用(设为null),当线程销毁后,那么指向ThreadLocalMap的那条强引用线就会断掉,这个时候ThreadLocalMap就没有强引用指向它,此时就算ThreadLocalMap中的key值是强引用指向ThreadLocal,也会被垃圾回收器回收掉(因为此时,已经无没有GCROOT对象指向他们了),这种情况,不会出现内存泄露
G4ubmV0L3dlaXhpbl80Njk2OTIzMw==,size_16,color_FFFFFF,t_70)

2、线程池使用ThreadLocal
线程池中的线程,使用ThreadLocal后,断开ThreadLocal的强引用(设为null),但是线程存在于线程池中,未被销毁。设想一下,把图中的弱引用换成强引用,如果此时ThreadLocalMap的key值是强引用指向ThreadLocal的,那么这个ThreadLocal,是不是就永远回收不掉呢。(弱引用:每一次GC时候,被若引用指向的对象,会被GC垃圾收集器回收掉)
在这里插入图片描述
3、value值内存泄露
通过上面的描述,可能就发现了问题,弱引用解决了key值内存泄露的问题,但是value值也存在内存泄露的可能性呀。 ThreadLocalMap中的value值,是强引用指向的它的,所以当key值被GC掉后,那么value值在一直存在于map中,并且还取不出来。这会儿,就需要我们在使用的过程中,使用完后,主动调用remove()方法,去清理掉ThreadLocalMap中的值,remove方法不仅仅可以清理掉value值,同时也会清理ThreadLocalMap中,所有无效的值。

总结

1、ThreadLocal主要用于保存当前线程的本地变量,可以起到变量之间的线程隔离,常用的场景就有数据库的会话连接、多数据源等。
1、在使用ThreadLocal的时候,注意在使用完后,主动remove(),避免内存泄露问题
2、在使用ThreadLocal的时候,使用static去标识, 原因是防止重复创建ThreadLocal对象,使用static标识后,静态变量只会创建一次。如果不使用static标识也可以,不会出现错误,但是会造成内存空间的浪费。
(以上是个人对ThreadLocal的源码剖析,还有理解,有差错的地方,欢迎指正)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值