ThreadLocal分析四:面试六连问

一.内存泄漏的原因

ThreadLocal操作不当会导致内存泄漏,为什么会内存泄漏呢?主要的原因在于她的内部类ThreadLocalMap里面Entry类的设计。

看过前几篇文章的都知道,Entry继承了WeakReference<ThreadLocal<?>>,表示这个Entry里面的key是个弱引用,所以key在垃圾回收的时候会被回收掉,但是,but。key对应的vule不会被回收,这样就会导致key为null,但是value不为空。因为这样,这个value你永远获取不到,久而久之,value的累加就会导致内存泄漏。

二.如何解决这个问题

那就是在每一次使用完ThreadLocal后,调用remove方法清除数据,因为她的remove方法会主动的清除key和value

private void remove(ThreadLocal<?> key) {
            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)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

e.clear()用于清除Entry的key,它调用的是WeakReference中的方法:this.referent = null

expungeStaleEntry(i)用于清除Entry对应的value, 这个后面会详细讲。

三、JDK开发者是如何避免内存泄漏的

        开发者也知道内存泄漏的问题存在,他们是通过在set、get方法中埋藏了清除的方法,如果key为null, 则会调用getEntryAfterMiss()方法,在这个方法中,如果k == null , 则调用expungeStaleEntry(i);方法。

        private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
            Entry[] tab = table;
            int len = tab.length;

            while (e != null) {
                ThreadLocal<?> k = e.get();
                if (k == key)
                    return e;
                if (k == null)
                    expungeStaleEntry(i);
                else
                    i = nextIndex(i, len);
                e = tab[i];
            }
            return null;
        }

expungeStaleEntry(i)方法完成了对key=null 的key所对应的value进行赋空, 释放了空间避免内存泄漏。同时它遍历下一个key为空的entry, 并将value赋值为null, 等待下次GC释放掉其空间。这样做, 也只能说尽可能避免内存泄漏, 但并不会完全解决内存泄漏这个问题。比如极端情况下我们只创建ThreadLocal但不调用set、get、remove方法等。所以最能解决问题的办法就是用完ThreadLocal后手动调用remove().

四、手动释放ThreadLocal遗留存储?你怎么去设计/实现?

这里主要强调一下手动remove的重要性和必要性,跟连接池类似

包装她的父类remove方法为静态方法,如果是Spring项目,那完全可以借助bean声明的时候,在拦截器afterCompletion阶段调用。注意,下面是你在面试的时候多要钱的资本。一定要理解!!!

五.弱引用导致内存泄漏,那为什么key不设置为强引用

如果key设为强引用,当ThreadLocal的实例被释放后,threadLocal=null,但是ThreadLocal会有强引用指向ThreadLocalMap,而ThreadLocalMap又有强引用Entry,而Entry里面的key又是ThreadLocal,这个key也是强引用的话,我们就无法对其回收。只有弱引用的对象,才会在下一次GC回收的时候才会被回收。事实上,当currentThread执行结束后, threadLocalMap变得不可达从而被回收,Entry等也就都被回收了。只要我们不主动释放ThreadLocal变量,她所引用的Entry中的ThreadLocal对象就不会被释放。在线程池场景中使用ThreadLocal是有内存泄露的可能性的,原因就是线程池的核心线程Thread是循环利用的,每个线程对应的ThreadLoalMap被强引用着,所以每个线程的ThreadLoalMap不能被回收,但是ThreadLoalMap里含有多个ThreadLocal-value的Entry,虽然ThreadLocal-key是弱引用可以被垃圾回收器自动回收,但是ThreadLocal对应的value是不能被回收的,所以说有内存泄露的情况可能性。这样会导致threadlcoal不能正常的被垃圾回收弱引用虽然会引起内存泄漏, 但这个环境就要求不对Thread进行复用,但是我们项目中经常会复用线程来提高性能, 所以currentThread一般不会处于终止状态。

六Spring如何处理Bean多线程下的并发问题

要想实现jdbc事务, 就必须是在同一个连接对象中操作, 多个连接下事务就会不可控, 需要借助分布式事务完成。那spring 如何保证数据库事务在同一个连接下执行的呢?

DataSourceTransactionManager 是spring的数据源事务管理器, 它会在你调用getConnection()的时候从数据库连接池中获取一个connection, 然后将其与ThreadLocal绑定, 事务完成后解除绑定。这样就保证了事务在同一连接下完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值