ThreadLocal 源码浅析

本文深入解析ThreadLocal的工作机制,包括线程隔离原理、hash冲突处理方式、内存泄漏原因及预防措施,帮助理解ThreadLocalMap内部结构。

ThreadLocal大家应该都经常用到,比如多租户模式,动态配置多数据源,源码就不逐句分析了,想必大家都看过,但是具体相关问题,有些也和我一样比较模糊,今天做下记录,带着问题看源码

1.为什么ThreadLocal能看到做到线程隔离

直接上源码看注释(ThreadLocal 部分源码)

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t); #这里获取Map
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); #这里的key并不是thread对象,而是当前的ThreadLocal对象
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
      return t.threadLocals; # 获取的map为当前线程的实例变量,所以不同线程,互相不影响
  }

线程的源码
在这里插入图片描述

总结: 为什么不同线程互不影响,能做到线程隔离,因为虽然ThreadLocal是公共变量,但是ThreadLocal调用get,set 方法时,最后操作的都是当前线程的实例变量的ThreadLocalMap,不同线程操作的都是不同线程自身的实例变量ThreadLocalMap,当然是互不影响,同时,虽然是不同线程,但是操作时的ThreadLocalMap对象的key都是当前ThreadLocal这个对象。

2.ThreadLocalMap 只是entry数组,没有加链表,怎么防止hash冲突的?
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)]) { #如果有hash冲突,直接到下标下一个
                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();
        }

        /**
         * Increment i modulo len.
         */
        private static int nextIndex(int i, int len) {
            return ((i + 1 < len) ? i + 1 : 0); #返回数组下一个位置
        }

总结:
当出现hash冲突时,直接放到数组下一位

3.为什么会出现内存泄漏的问题
 # ThreadLocalMap 的entry源码
 
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

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

在这里插入图片描述

TreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链,永远无法回收,造成内存泄漏。

4.为什么不用这个key不用强引用

Key使用强引用:引用ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为强引用,并没有被回收,如果不手动回收的话,ThreadLocal将不会回收那么将导致内存泄漏。
Key使用弱引用:引用的ThreadLocal的对象被回收了,ThreadLocal的引用ThreadLocalMap的Key为弱引用,如果内存回收,那么将ThreadLocalMap的Key将会被回收,ThreadLocal也将被回收。value在ThreadLocalMap调用get、set、remove的时候就会被清除。
比较两种情况,我们可以发现:由于ThreadLocalMap的生命周期跟Thread一样长,如果都没有手动删除对应key,都会导致内存泄漏,但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

5.那么如何有效的避免呢?

事实上,在ThreadLocalMap中的set/getEntry方法中,会对key为null(也即是ThreadLocal为null)进行判断,如果为null的话,那么是会对value置为null的。我们也可以通过调用ThreadLocal的remove方法进行释放!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值