了解ThreadLocal

1.ThreadLocal能干什么

ThreadLocal是Java中的一个类,它可以解决多线程环境下共享变量的问题。每个ThreadLocal对象都可以保存一个线程私有的变量副本,这样每个线程对变量的操作都只会影响到自己的变量副本,不会影响其他线程的变量。

ThreadLocal可以用于以下场景:

  1. 线程上下文的传递:在多线程环境中,有时候需要在不同的方法或类中传递一些上下文信息,如用户登录信息、请求ID等。通过ThreadLocal可以将这些上下文信息与线程关联起来,让线程中的其他方法或类可以方便地获取这些信息,避免了传递参数的麻烦。

  2. 数据库连接管理:在使用数据库连接池时,每个线程需要获取一个独立的数据库连接,通过ThreadLocal可以将每个线程的数据库连接保存在ThreadLocal对象中,确保每个线程使用自己的数据库连接。

  3. 事务管理:在使用事务管理框架时,有时需要在一个事务中共享某个变量,但又不希望这个变量被其他线程修改。通过ThreadLocal可以将这个变量保存在ThreadLocal对象中,确保每个线程只能访问自己的变量副本。

总之,ThreadLocal可以提供一种线程级别的变量隔离机制,有效解决多线程环境下共享变量的问题。但需要注意的是,ThreadLocal并不能解决多线程并发访问共享资源的问题,只能实现线程间的数据传递和隔离。因为,如果多个线程共享一个资源,而且每个线程都通过ThreadLocal获取到自己的副本,并对副本进行读写操作,那么实际上每个线程都只能访问自己的副本,而无法对真正的共享资源进行操作和同步。

2.源码分析

2.1 Thread

//Tread 类中定义了这两个变量
  
     //  ThreadLocalMap
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * 父线程不能能用ThreadLocal来给子线程传值,这个可以。
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

2.2ThreadLocalMap

我们看看ThreadLocalMap的结构。

key是弱引用,这也是为什么会造成内存泄露的问题。

弱引用与强引用不同,弱引用并不会阻止其所引用的对象被垃圾回收。当垃圾回收器扫描到某个对象只被弱引用所引用时,即使内存空间不足,也会将该对象回收。但是此时value还在,这就会造成了内存泄漏问题。

    static class ThreadLocalMap {

        /**
          *  定义了一个Entry ,并且继承了弱引用,key是ThreadLocal,value是传参进来的值。
         *
         */
        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


        // 初始化entry数组
        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);
        }
}

2.3 ThreadLocal的Hash

这段代码中,threadLocalHashCode 的计算利用了斐波那契数(0x61c88647)作为增量,这是一种优化手段,可以使得线程本地变量的哈希码在哈希表中分布更加均匀,避免哈希冲突。

斐波那契数(Fibonacci Number)是一种数学序列,它的特点是每个数等于前两个数的和。而黄金分割数(Golden Ratio)则是斐波那契数列中相邻两个数的比值,近似为1.618。

使用斐波那契数作为增量的优点在于,它会使得哈希值的变化更加均匀,从而提高了哈希表的效率和性能。

因此,这段代码的作用是为每个线程本地变量生成独特的哈希码,并通过斐波那契数作为增量来保证哈希码的分布更加均匀,从而提高了线程本地变量的访问效率。

2.4ThreadLocalMap的set源码

我们再来看看set的核心源码。

先计算出下标i,如果entry数组中不存在,就直接赋值,如果存在就进行比较。

我们可以看到判断中有一个nextIndex方法。这个方法主要是采用了开放定址法,就是位置被别人占了,就继续寻找下一个位置去填充。

而replaceStaleEntry,确保 ThreadLocalMap 中线程本地变量的槽位能够正确存放最新的条目,并清理过时的条目以释放内存。

    private void set(ThreadLocal<?> key, Object value) {
            
            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;
            // 清理过期元素 ,如果没有需要清理的
            // 并且 长度大于阈值(len * 2 / 3)调用rehash()
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0);
        }

        private void setThreshold(int len) {
            threshold = len * 2 / 3;
        }

2.5 ThreadLocalMap扩容机制

扩容机制比较简单。

就是扩容原来长度两倍,然后重新填充数组。

        private void rehash() {
            expungeStaleEntries();
            //触发扩容,大于3/4.
            if (size >= threshold - threshold / 4)
                resize();
        }


        private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            //原来长度的两倍
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            //遍历数组,然后重新赋值。
            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
        
            setThreshold(newLen);
            size = count;
            table = newTab;
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Mikey689

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值