ThreadLocal 简单使用及内存泄漏的原理分析

ThreadLocal

1.简介

ThreadLocal是为了解决不同线程在处理同一个类的时候,出现变量不安全的问题。每个线程会保存一个变量的副本互相不影响,所以是对线程隔离的。

2.ThreadLocal使用方法

public class ThreadLocalTest() {
    private static ThreadLocal<String> strLocal = new ThreadLocal();
    
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                strLocal.set("ThreadLocal---1");
                TimeUnit.SECONDS.sleep(2);
                System.out.println(strLocal.get());
            }
        }).start();
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                strLocal.set("ThreadLocal---2");
                TimeUnit.SECONDS.sleep(1);
                System.out.println(strLocal.get());
            }
        }).start();
    }
}

运行可以看出,两个线程分别存储了自己的strLocal,拿到的值也是各自存储的样子。

3.原理分析

在执行threadLocal.set()方法的时候,会在每个线程类Thread内部,去生成每个线程独立的ThreadLocalMap对象,这个对象就是用来具体存值的。存值采用的是key-value形式的Entry,这个Key就是对ThreadLocal的引用,类似于指针。get()方法也比较类似,会根据当前线程的id去获取线程中的ThreadLocalMap对象,然后根据ThreadLocal的引用去拿value。所以,就实现了线程隔离。

4.ThreadLocal内存泄漏

ThreadLocal是存在内存泄漏风险的。原因是:因为ThreadLocalMap中存的Key(存在Entry中的key-value)是指向ThreadLocal对象的指针(弱引用),如果在某个时间,ThreadLocal被使用完了(没有指向ThreadLocal的强引用),ThreadLocal就会GC回收了,也就是说,ThreadLocalMap中的Key变成了null,但是value却依然存在,这个Entry中的value就会造成内存泄漏。为什么不适用强引用?因为使用了强引用,在使用ThreadLocal没有被对象强引用的时候,但是此时线程还是存在,那么就会有堆中的ThreadLocal本身的内存泄漏了,因为Entry中还是有对他的强引用,所以一直都不会被回收。

avatar以上是总结的ThreadLocal内存使用图,可以简单点的解释到底是怎么个布局。

其中,需要注意的是,ThreadLocalMap的生命周期是和Thread保持一致的,所以只要这个线程一直运行,那么ThreadLocalMap就会一直存在。但是线程对于每个ThreadLocal的强引用却不会一直存在,如果这个强引用失效了,那么堆中ThreadLocal就会在下次GC被回收掉,这个时候,Entry中的弱引用就是null了。为了避免这个问题,最好的方式就是每次含有ThreadLocal的类在get()完之后,手动remove掉。 同时,在调用ThreadLocal的get、set、remove方法的时候也会去检查key为null的Entry,并将value释放掉。但是如果设置了一个key之后一直不再使用set、get、remove,那么就会有内存泄漏问题了。

private boolean cleanSomeSlots(int i, int n) {
    boolean removed = false;
    Entry[] tab = table;
    int len = tab.length;
    do {
        i = nextIndex(i, len);
        Entry e = tab[i]; 
        if (e != null && e.refersTo(null)) {
            n = len;
            removed = true;
            i = expungeStaleEntry(i);
        }
    } while ( (n >>>= 1) != 0);
    return removed;
}

private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;

    // expunge entry at staleSlot
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // Rehash until we encounter null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;

                // Unlike Knuth 6.4 Algorithm R, we must scan until
                // null because multiple entries could have been stale.
                while (tab[h] != null)
                    h = nextIndex(h, len);
                tab[h] = e;
            }
        }
    }
    return i;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值