• 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就会导致内存泄漏,而不是因为弱引用。

  • 相关阅读:
    “图灵&博客园&互动网有奖书评征集活动——微软技术系列”评选结果
    像优秀的SQL程序员一样思考
    倚天·屠龙——唯我独尊
    CSS与HTML设计模式全集(350余种)
    游览器兼容冲突的常见css
    嵌入多媒体文本
    删除确认代码
    用!important解决IE和Mozilla的布局差别
    四大游览器兼容问题综合实例
    jQuery事件之鼠标事件
  • 原文地址:https://www.cnblogs.com/bbgs-xc/p/13436582.html
Copyright © 2020-2023  润新知