ThreadLocal与内存泄漏

格式化版本

https://www.wolai.com/tB2tDxayzP1E1oWNsQBAXo

 

 

原理

文字简述:

每个Thread都有一个map(ThreadLocalMap)

当调用ThreadLocal的set方法时会往Thread的map里塞一个key为ThreadLocal本身,value为设置的值的元素,这里的map的key对ThreadLocal本身是个弱引用

基于此,Thread实现了线程隔离

内存泄漏

场景1

static ThreadLocal<String> string= new ThreadLocal<String>();
string.set("123");
string=null

在此场景下,new ThreadLocal<String>()创建出来的堆对象失去了string指针对其的强引用

又由于ThreadLocalMap的key对其的引用是弱引用

所以在gc之后,该堆对象将会被回收

所以ThreadLocalMap里面将会存在一个key为null,value值为123的元素

且该value因为存在ThreadLocalMap的强引用,所以该value会一直存在,发生内存泄漏

(ThreadLocalMap做了优化,在set,get,remove的场景下都会清空map里key为null的value)

场景2

public class ThreadLocalDemo {
    static class LocalVariable {
        private Long[] a = new Long[1024 * 1024];
    }

    // (1)
    final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,
            new LinkedBlockingQueue<>());
    // (2)
    final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<LocalVariable>();

    public static void main(String[] args) throws InterruptedException {
        // (3)
        Thread.sleep(5000 * 4);
        for (int i = 0; i < 50; ++i) {
            poolExecutor.execute(new Runnable() {
                public void run() {
                    // (4)
                    localVariable.set(new LocalVariable());
                    // (5)
                    System.out.println("use local varaible" + localVariable.get());        
                }
            });
        }
        // (6)
        System.out.println("pool execute over");
    }
}

在此场景下,由于线程池里的5个线程一直存活,所以每个thread里的ThreadLocalMap一直存在对value的强引用,所以发生value的内存泄漏

跨线程场景

InheritableThreadLocal可以解决A线程里启动B线程时获取A线程的ThreadLocal的问题,可以将A线程的ThreadLocalMap的值赋给B线程(谁创建子线程,set谁的thredlocal)

TransmittableThreadLocal可以解决A线程创建了B线程,在C线程里调用B线程获取C线程的ThreadLocal的问题(谁调用子线程,set谁的thredlocal)

线程安全问题

ThreadLocal不是用来解决线程安全问题,如果ThreadLocal的value为引用对象,多线程对该引用对象的操作一样会有线程安全的问题(引用传递)

参考文章

posted @ 2022-04-01 17:06  MRLL  阅读(70)  评论(0编辑  收藏  举报