ThreadLocal:使用例子、原理、应用场景、内存泄漏的原因

ThreadLocal在Java中用于为每个线程提供独立的变量副本,确保线程安全。然而,由于ThreadLocalMap的Entry使用弱引用来保存ThreadLocal实例,可能导致内存泄漏。当ThreadLocal不再被引用时,GC会回收ThreadLocal对象,但其对应的value因强引用不会被回收,造成内存浪费。SpringMVC等框架中使用ThreadLocal处理线程上下文信息时需注意这一点。为防止内存泄漏,ThreadLocal的set、get、remove操作会清理过期Entry。
摘要由CSDN通过智能技术生成

1.使用例子:

public class ThreadLocalExample {



    public static void main(String[] args) {
        //每个线程设置变量,相当于设置到ThreadLocalMap中的Entry中(key是线程名字,value是设置的值)
        //但是key是弱引用,value是强引用,当GC的时候会回收key,但是value还在,导致内存泄漏
        //下次线程再次创建会创建新的,所以内存泄漏了
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        for (int i=0;i<10;i++){
            new Thread(()->{
                System.out.println("线程名字"+Thread.currentThread().getName());
                threadLocal.set(Thread.currentThread().getName()+"**");
                System.out.println(threadLocal.get());
                System.out.println("-----------");
            }).start();
        }
        //线程名字Thread-8
        //Thread-8**
        //-----------
        //线程名字Thread-9
        //Thread-9**
        //-----------
    }
}

2.原理:ThreadLocal类有内部类ThreadLocalMap,ThreadLocalMap类有内部类Entry(key是线程名字,value是每个线程设的值)

ThreadLocal主要功能:每个线程提供一个专属的局部变量(一个线程的变量对应ThreadLocalMap中自己线程名字的Entry中的value)
因为是专属的,所以是线程安全的

ThreadLocal类的内部类set()

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocal类的内部类ThreadLocalMap:


static class ThreadLocalMap {
		//Entry就是存ThreadLocalMap的key和value。
		//key是弱引用,gc时不管内存够不够都会回收这个对象,而value是强引用,不会回收。导致内存泄漏
		//当同一个线程再次创建专属变量时发现不存在又新建,但是老的value对象没有回收,就出现了内存泄漏。
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

内部类ThreadLocalMap的set(this,value)方法:

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;
                }
            }
			//key和value赋值给ThreadLocalMap类的内部类Entry:看上面的Entry是继承WeakReference,是弱引用,GC的时候回收key
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

3.应用场景

每个线程设置自己的变量,获取的也是自己的变量

SpringMVC单例的情况下,controller中可以加ThreadLocal对象,用来给不同线程设置自己的专属变量,解决线程安全

4.内存泄漏的原因

ThreadLocalMap类的内部类Entry:是继承WeakReference,是弱引用,GC的时候回收key。但是value没有回收,导致了内存泄漏。
同样的线程下次再次设值得时候发现key没有,然后新建,老的value一直在,所以内存泄漏

ThreadLocal提供了解决方案:

每次操作set、get、remove操作时,会相应调用 ThreadLocalMap 的三个方法,ThreadLocalMap的三个方法在每次被调用时 都会直接或间接调用一个 expungeStaleEntry() 方法,这个方法会将key为null的 Entry 删除,从而避免内存泄漏。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值