ThreadLocal深入解读

1、ThreadLocal是什么

ThreadLocal是多线程环境下,为了变量安全提供的一种解决方案。ThreadLocal提供了线程的局部变量,每个线程都可以通过set()和get()来对这个局部变量进行操作,但不会和其他线程的局部变量进行冲突,实现了线程之间的数据隔离。

往ThreadLocal中填充的变量属于当前线程,该变量对其他线程而言是隔离的。

import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ThreadLocalTest {

    public static ThreadLocal<String> localVar = new ThreadLocal<>();

    public static void print() {
        //获取当前线程中本地变量的值
        String before = localVar.get();
        //清除当前线程中本地变量的值
        localVar.remove();
        //再次获取当前线程中本地变量的值
        String after = localVar.get();
        //打印
        System.out.println("before remove method var:[" + before + "] after remove method var:[" + after + "]");
    }

    public static void main(String[] args) {
        //定义一个线程池
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(20, 20, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(1024));
        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.execute(() -> {
                //设置线程中本地变量的值
                localVar.set("var=" + Thread.currentThread().getId());
                //调用打印方法
                print();
            });
        }
        threadPoolExecutor.shutdown();
    }

}

在这里插入图片描述

上面这段代码就可以表现出ThreadLocal变量变量线程隔离的特点。

2、ThreadLocal可以做什么

2.1、避免一些参数传递

想象下同一线程顺序执行下来,方法的层级调用关系很深。但是很多地方都需要一个共同变量A,该如何解决?
我们可以通过参数传递一层一层向下传递,某些中间方法即便不需要该变量A,也依然要帮忙把变量传递下去。这样我们的代码结构就显得非常臃肿和难看了。
而如果采取ThreadLocal线程变量的方式,则可以避免这样的参数传递,可以通过ThreadLocal很方便的设置和获取需要的变量。

2.2、管理Connection

经典的使用场景是为每个线程分配一个 JDBC 连接 Connection。这样就可以保证每个线程的都在各自的 Connection 上进行数据库的操作,不会出现 A 线程关闭了 B线程正在使用的 Connection; 还有 Session 管理等问题。实际就是利用ThreadLocal线程变量的隔离性,独占某个资源。

3、ThreadLocal实现的原理

本文使用的是JDK 1.8

3.1、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 map = getMap(t);//通过当前Thread获得一个ThreadLocalMap对象
        if (map != null)
            //如果不为空,则设置值。key为当前定义的ThreadLocal变量的this引用
            map.set(this, value);
        else
            //如果为空,则新建,并且设置当前value
            createMap(t, value);
    }
    
    /**
     * 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;
    }
    
	/**
     * 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);
    }
  • set(T value)的实现看起来挺简单,主要是通过一个ThreadLocalMap来进行操作的。
  • ThreadLocalMap的持有者是Thread t线程对象,所以说变量线程的隔离性就是这么来的。
  • ThreadLocal类就像是一个代理,各种操作实际都是操作当前Thread t线程对象的ThreadLocalMap threadLocals局部变量。
  • 可以说搞清楚了ThreadLocalMap就了解了ThreadLocal是怎么回事了。

3.2、ThreadLocalMap类深入解读

/**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {

		/**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;
		
        /**
         * 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.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        
        /**
         * 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.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

ThreadLocalMap定义在ThreadLocal中的一个静态内部类,但对象的引用却是在Thread中
ThreadLocalMap内部使用的是Entry[] table来保存线程变量的,对于Entry来说key是当前ThreadLocal对象,value是传递进来的对象

在这里插入图片描述

3.2.1、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.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

我们看到这个Entry类的定义,正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用。
弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
就比如线程池里面的线程,线程都是复用的,那么之前的线程实例处理完之后,出于复用的目的线程依然存活,所以ThreadLocal设定的value值被持有,导致内存泄露。按照道理一个线程使用完,ThreadLocalMap是应该要被清空的,但是现在线程被复用了。

解决方法,就是在使用完ThreadLocal后,调用remove方法

ThreadLocal<String> localVar= new ThreadLocal();
try {
    localVar.set("张三");
    ……
} finally {
    localVar.remove();
}

3.2.2、Entry[] table的存取逻辑

  • 根据ThreadLocal对象的hash值,定位到table中的位置 i,int i = key.threadLocalHashCode & (len-1)
  • 如果位置 i 不为空,如果这个Entry对象的key正好是即将设置的key,那么就覆盖Entry中的value
  • 如果当前位置是空的,就初始化一个Entry对象放在位置 i 上
  • 如果位置 i 不为空,而且key不等于entry,那就找下一个空位置,直到为空为止

3.3、ThreadLocal的get()方法

理解了ThreadLocal的set()方法和ThreadLocalMap类之后,就很容易理解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);//先获得当前线程Thread的ThreadLocalMap对象
        if (map != null) {
        	//根据当前ThreadLocal对象作为Key来获得Entry对象
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;//Entry对象的value
                return result;
            }
        }
        return setInitialValue();
    }

3.4、ThreadLocal原理总结

  • 每个Thread维护着一个ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry来存储键值对,用Entry[] table数组来存储同一线程中多个ThreadLocal对象的键值对。
  • 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是当前ThreadLocal对象,值是传递进来的对象。
  • 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是当前ThreadLocal对象。
  • ThreadLocal本身并不存储值,它只是作为一个key,让线程从ThreadLocalMap中获取对应的value。

4、弱引用

  • threadlocal value为什么不是弱引用?

关于key设计成弱引用的理解,总结起来就是:既然ThreadLocal不让开发者关心key的处理,那么它自己就应该负责key的管理;

value作为开发者外部传入的值,那么value的管理应该由开发者自己负责。

假如value被设计成弱引用,那么很有可能当你需要取这个value值的时候,取出来的值是一个null。你使用ThreadLocal的目的就是要把这个value存储到当前线程中,并且希望在你需要的时候直接从当前线程中拿出来,那么意味着你的value除了当前线程对它持有强引用外,理论上来说,不应该再有其他强引用,否则你也不会把value存储进当前线程。但是一旦你把本应该强引用的value设计成了弱引用,那么只要jvm执行一次gc操作,你的value就直接被回收掉了。当你需要从当前线程中取值的时候,最终得到的就是null。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值