简述 ThreadLocal 原理

简述ThreadLocal 使用和原理

在线程的调用中可以通过ThreadLocal进行的参数传输,减少方法中的参数一层层的嵌套下去,而且它是线程安全的,通过使用ThreadLocal 的方式可以在代码和多线程的情况下处理很多线程安全的问题。

那它是怎样做到在线程里面里面做到线程安全的呢?

那它是通过怎样的格式保存数据的呢?

存在多数据的时候是否会导致内存泄露呢?

接下来将探讨下ThreadLocal底层的实现

简单 main 方法示例:

public class TreadLocalDemo {


    private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
    private static ThreadLocal<String> threadLocal2 = new ThreadLocal<>();

    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{
            threadLocal1.set("11111");
            threadLocal2.set("22222");
            System.out.println(threadLocal1.get());
            System.out.println(threadLocal2.get());
            threadLocal2.remove();
        }).start();
    }
}

得到结果:

 

这里可以看到 通过set方法就可以 在同一个线程下面每个地方可以通过获取到threadLocal 的引用就可以通过Get 方法获取到set进去的值.

上面只是简单示范下,你学会后可以有各种各样的变体。

这里是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);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

这里可以看到通过获取到当前Thread 然后获取之前是否已经存在了ThreadLocalMap 对象;

如果当前线程没有ThreadLocalMap 就会创建一个,并且把引用设置会Thread 对象里面,创建ThreadLocalMap 时初始化一个默认值为16的 Entry[]  ,并且Entry对应的key就是当前ThreadLocal的引用,而且是个弱应用(后面会描述他们的关系),value就是set的值。

        void createMap(Thread t, T firstValue) {
            t.threadLocals = new ThreadLocalMap(this, firstValue);
        }

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        	// 创建一个默认长度等于16的数组
            table = new Entry[INITIAL_CAPACITY];
            // 通过当前ThreadLocal hashcode值 和 当前的容量 取 & 
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        	// 设置值
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            // 是这里计算下一次扩展临界值长度
            setThreshold(INITIAL_CAPACITY);
        }

关于 firstKey.threadLocalHashCode 计算方式可以参考: https://zhuanlan.zhihu.com/p/40515974  

如果当前对象已经存在ThreadLocalMap的话 

/**
         * 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();
        }

 

这里代码的大体已经就是通过计算出当前threadLocal的索引值 然后把值设置到对应的ThreadLocalMap的Entry对应的数组中。

这里描述ThreadLocal 、ThreadLocalMap、Entry[]、Entry 它们的关系:

一个Thread,里面只有一个THreadLocalMap,在ThreadLocalMap 里面所以会有一个Entry[],而且在创建Entry对象的时候 Key是threadLcoal、value 是设置的值

 

 

 

上图可以说明:

如果我们当前Thread 执行完成后就会断开强引用,只有一个弱引用,这样弱引用对象来说就会被垃圾回收机制给回收,防止内存溢出。

难道这样就不会产生内存溢出吗?假如设置 threadLocal = null ,而且当前Thread还存活这时候。Entry对象里面的Key是弱应用他会不会被回收 ? 不会(因为它本身其实是线程本身强引用,只有当不存在线程的强引用之后,这个weakreference才会在GC的时候被端),但是这时候Entry 对象里面的 key = null 这样下来不一样会存在不断的内存占用吗? 一样有可能导致内存的溢出。

当然上面说的设计者也想到了,所以分别在执行set、get、remove的方式里面都会尝试或者直接去检查key = null 的值去移除。(这里并不是所有的set、get操作都会去执行,remove 是先设置成null然后去移除 可以去看看源码里面的这个方法 expungeStaleEntry )

也许你留意到我上面的main方法的代码里面的ThreadLocal是一个静态的对象,如果你把他加上一个final 的话 ,上面是不是就成为一个伪命题了。。。具体实现Thread 写法很多。但是建议最后还是我们通过手动remove() 方法进行移除。 

 

可能有人会问为什么需要把存储对应格式设计成数组,那你可以看看我一开始的main方法,因为一个Thread里面可以持有一个或者多个以上的ThreadLcoal的引用,这样的话就需要数组来进行存储了。

 

举例使用场景:

1、spring使用ThreadLocal解决线程安全问题我们知道在一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,绝大部分Bean都可以声明为singleton作用域。就是因为Spring对一些Bean(如 RequestContextHolderTransactionSynchronizationManagerLocaleContextHolder等)中非线程安全状态采用ThreadLocal进行处理让它们也成为线程安全的状态。
2、PageHelper找那个startPage就是通过ThreadLocal来进行保存当前分页信息。

 

这里只是本人对ThreadLocal一些简单的见解,如果有错误之处请谅解,望指出,谢谢!!!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值