ThreadLocal原理分析与常见问题

基础知识:
ThreadLocal 内部是用一个map结构(threadLocalMap),key为线程对应的ThreadLocal对象,value为业务对象,可以将多个线程隔离开来。

先看类图:
在这里插入图片描述
重点看一下threadLocalMap的源码,它是一个简单的entry数组,键值key为线程对应的ThreadLocal对象,value为业务对象,在数组中的存储位置由key的threadLocalHashCode对INITIAL_CAPACITY取余数得到。

 static class ThreadLocalMap {

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

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0

        /**
         * Set the resize threshold to maintain at worst a 2/3 load factor.
         */
        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        /**
         * Decrement i modulo len.
         */
        private static int prevIndex(int i, int len) {
            return ((i - 1 >= 0) ? i - 1 : len - 1);
        }

        /**
         * 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) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

问题1: 内部map为何不用HashMap或者ConcurrentHashMap来实现?
ThreadLocal在实现上,是让每个线程在自己的内部单独持有一个变量,线程之间就不会有竞争出现,所以不需要采用并发对象ConcurrentHashMap。

不使用HashMap原因
ThreadLocalMap不存在多个key具有相同hashcode的问题,因而数据存储不需要像hashmap那样设计成数组+链表的形式,只需要数组就可以。
一个线程生成后,很久没有再次调用,我们为了保证系统的可用性,需要对线程资源进行回收,因而ThreadLocalMap的entry是需要继承 WeakReference的,便于即使回收。

问题2:threadLocalHashCode到底用来做什么的?

这个参数,我们是用来唯一确定一个ThreadLocal对象的

但是如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性呢?在ThreadLocal类中,还包含了一个static修饰的AtomicInteger成员变量和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。

确定了唯一的ThreadLocal对象,threadLocalHashCode还作为确定当前线程的ThreadLocalMap的table数组的位置(table数组其实就是Entry数组)

问题3:为什么不直接用线程id来作为ThreadLocalMap的key?

这一点很容易理解,因为直接用线程id来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。比如我们放入了两个字符串,你如何知道我要取出来的是哪一个字符串呢?

而使用ThreadLocal作为key就不一样了,由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分,所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。

问题4:ThreadLocal使用场景与存在的风险?

在保持用户登录态的这个需求下,一般用 ThreadLocal 存储用户信息,而不是采用常见的 Cookie + Session。参考:threadLocal优势

存在的风险:
1:脏读(脏数据): 在一个线程中读取到了不属于自己的数据 。
线程使用 ThreadLocal 不会出现脏读,每个线程都是用的是自己的变量值和 ThreadLocal
线程池使用 ThreadLocal 会出现脏数据,线程池会复用线程,复用线程之后,也会复用线程中的静态属性,从而导致某些方法不能被执行,于是就出现了脏数据的问题
2.ThreadLocal 内存溢出问题(最常出现的问题)
内存泄漏:当一个线程执行完之后,不会释放这个线程所占用内存,或着释放内存不及时(线程不用了,但线程相关内存还得不到及时释放

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值