ThreadLocal刨根问底

一、ThreadLocal使用场景

  在数据库使用connection对象时,每个客户都能使用自己的connection对象,防止出现客户ClientA操作关闭ClientB的connection连接对象。

  案例:https://zhuanlan.zhihu.com/p/82737256

二、ThreadLocal中的remove()使用

  1,防止内存泄露

  2,线程不安全

    在ThreadLocal和线程池联合使用的时候,会出现下个业务请求复用到上一个线程的情况,导致使用相同的ThreadLocal执行不同的业务逻辑。

public class ThreadLocalAndPool {
    private static ThreadLocal<Integer> variableLocal = ThreadLocal.withInitial(() -> 0);
    public static int get() {
        return variableLocal.get();
    }
    public static void remove() {
        variableLocal.remove();
    }
    public static void increment() {
        variableLocal.set(variableLocal.get() + 1);
    }

    public static void main(String[] args) {
        ExecutorService executorService = new ThreadPoolExecutor(2, 2, 0, TimeUnit.SECONDS, new LinkedBlockingDeque<>(12));
        for (int i = 0; i < 5; i++) {
            executorService.execute(() -> {
                long threadId = Thread.currentThread().getId();

                int before = get();
                increment();
                int after = get();
                System.out.println("threadid " + threadId + "  before " + before + ", after " + after);
            });
        }
        executorService.shutdown();
    }
}
threadid 12  before 0, after 1
threadid 13  before 0, after 1
threadid 12  before 1, after 2
threadid 13  before 1, after 2
threadid 12  before 2, after 3

三、为什么会出现内存泄露?

  ThreadLocal在ThreadLocalMap中是以一个弱引用身份被Entry中的Key引用的,因此如果ThreadLocal没有外部强引用来引用它,那么ThreadLocal会在下次JVM垃圾收集时被回收。这个时候就会出现Entry中Key已经被回收,出现一个null Key的情况,外部读取ThreadLocalMap中的元素是无法通过null Key来找到Value的。因此如果当前线程的生命周期很长,一直存在,那么其内部的ThreadLocalMap对象也一直生存下来,这些null key就存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry(table)-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。

四、为什么使用弱引用?

  从表面上看,发生内存泄漏,是因为Key使用了弱引用类型。但其实是因为整个Entry的key为null后,没有主动清除value导致。为什么使用弱引用而不是强引用?

官方文档的说法:

To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys.
为了处理非常大和生命周期非常长的线程,哈希表使用弱引用作为 key。

下面我们分两种情况讨论:

key 使用强引用:引用的ThreadLocal的对象被回收了,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
key 使用弱引用:引用的ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set,get,remove的时候会被清除。

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

因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key的value就会导致内存泄漏,而不是因为弱引用。

posted @ 2020-08-04 22:13  MXC肖某某  阅读(141)  评论(0编辑  收藏  举报