ThreadLocal原理

文章目录

    目录

    介绍

    原理

    底层代码

    存在的潜在问题

    内存泄漏问题

    数据污染问题

    使用场景


    介绍

            ThreadLocal 是为解决多线程程序的并发问题提供了一种新的思路,ThreadLocal 为每个线程提供独立的数据副本,避免多个线程共享变量时的竞态条件。

            在JDK1.2版本中就提供了java.lang.ThreadLocal;在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>,API方法也相应进行了调整。

    原理

            每个Thread对象都有一个ThreadLocalMap(称为 threadlocals),底层存储结构为 Entry[],当创建一个ThreadLocal的时候,就会将 ThreadLocal 对象添加到该Map中,其中键就是ThreadLocal,值是数据的副本对象。其中,key的引用为弱引用,这样设计的目的是防止内存泄漏。

    底层代码

    • 核心方法:
    public T get() { } // 用来获取ThreadLocal在当前线程中保存的变量副本
    public void set(T value) { } //set()用来设置当前线程中变量的副本
    public void remove() { } //remove()用来移除当前线程中变量的副本
    protected T initialValue() { } //initialValue()是一个protected方法,一般是用来在使用时进行重写的
    
    • 关键数据结构:
    // Thread类中的核心成员
    public class Thread implements Runnable {
        ThreadLocal.ThreadLocalMap threadLocals = null;
    }
    
    // ThreadLocalMap内部类
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);  // Key是弱引用
                value = v;  // Value是强引用
            }
        }
        private Entry[] table;
    }
    • set()方法工作流程:
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t); // 获取当前线程的ThreadLocalMap
        if (map != null) {
            map.set(this, value); // this指当前ThreadLocal对象
        } else {
            createMap(t, value); // 首次使用创建Map
        }
    }
    
    // ThreadLocalMap.set()核心逻辑
    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) { // 清理过期Entry
                replaceStaleEntry(key, value, i);
                return;
            }
        }
        tab[i] = new Entry(key, value); // 新建Entry
    }
    • get()方法实现:
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            Entry e = map.getEntry(this); // 以ThreadLocal为Key查找
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); // 初始化值(默认null)
    }

    存在的潜在问题

    内存泄漏问题

    • 原因:ThreadLocal 实例被回收,但Entry 中的 Value 仍然被强引用且未手动删除,同时线程并未销毁,例如使用线程池导致线程未销毁。
    • 解决方法:不使用时手动remove。

    数据污染问题

    • 原因:使用线程池导致线程未销毁,某个线程在任务1设置ThreadLocal 值后,又被该线程执行任务2获取值,可能读取到任务1设置的值。
    • 解决方法:线程池中慎用。

    使用场景

    1. 线程上下文传递(如Spring的RequestContextHolder

    2. 数据库连接管理(Connection per Thread)

    3. SimpleDateFormat线程安全封装

    4. 用户会话信息存储

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值