关于ThreadLocal内存泄漏引起的思考
来源:互联网 发布:freeswitch java 编辑:程序博客网 时间:2024/05/22 04:27
概述
最近在对一个项目进行重构,用到了ThreadLocal。场景如下:外围系统会调用接口上传数据,在接口中要记录数据的变化Id,在上传数据完后需要集中在一个地方把这些Id以消息形式发送出去。
使用场景样例代码
public Result<Void> uploadOrder(TotalPayInfoVo totalPayInfoVo) { try { saveTotalPayInfoVo(totalPayInfoVo); //发送消息 UnitWork.getCurrent().pushMessage(); } catch (Exception e) { cashLogger.error("uploadOrder error,data: {}, error: {}", JSON.toJSONString(totalPayInfoVo), e); throw new RuntimeException("保存失败", e); } finally { UnitWork.clean();// } return ResultUtil.successResult();避免内存泄漏 }
ThreadLocal使用源码
/** * 工作单元,在同一个线程中负责记录一个事件或者一个方法或者一个事务过程中产生的变化,等操作结束后再处理这种变化。 */public class UnitWork { private UnitWork() { } private static ThreadLocal<UnitWork> current = new ThreadLocal<UnitWork>() { protected UnitWork initialValue() { return new UnitWork(); } }; /** * 状态变化的instance */ private Set<String> statusChangedInstances = new HashSet<>(); public void addStatusChangedInstance(String instance) { statusChangedInstances.add(instance); } /** * 推送消息 */ public void pushMessage() { for(String id : statusChangedInstances){ //异步发消息 } } public static UnitWork getCurrent() { return current.get(); } /** * 删除当前线程的工作单元,建议放在finally中调用,避免内存泄漏 */ public static void clean() { current.remove(); }}
思考问题
为了避免内存泄漏,每次用完做一下clean清理操作。发送消息的过程是异步的,意味着clean的时候可能和发送消息同时进行。那么会不会把这些Id清理掉?那么可能造成消息发送少了。要回答这个问题,首先要搞懂ThreadLocal的引用关系,remove操作做了什么?
ThreadLocal解读
ThreadLocal可以分别在各个线程保存变量独立副本。每个线程都有ThreadLocalMap,顾名思义,类似Map容器,不过是用数组Entry[]来模拟的。那么既然类似Map,肯定会存在Key。其实Key是ThreadLocal类型,Key的值是ThreadLocal的HashCode,即通过threadLocalHashCode计算出来的值。
这个Map的Entry并不是ThreadLocal,而是一个带有弱引用的Entry。既然是弱引用,每次GC的时候都会回收。
static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
而Key对应的value就是要保存在线程副本Object,这里指的就是UnitWork的实例。调用ThreadLocal的get方法时,首先找到当前线程的ThreadLocalMap,然后根据这个ThreadLocal算出来的hashCode找到保存线程副本Object。他们的关系对应如下:
ThreadLocal在remove的时候,会调用Entry的clear,即弱引用的clear方法。把Key->ThreadLocal的引用去掉。接下来的expungeStaleEntry会把entry中value引用设置为null。
/** * Remove the entry for key. */ 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; } } }
现在可以回答之前提前的问题。虽然ThreadLocal和当前线程都会与Object脱离了引用的关系,但是最重要一点就是异步的线程仍然存在一条强引用路径到Object,即到UnitWork实例的强引用。因此GC然后不会回收UnitWork的实例,发消息还是不会少发或者出现空指针情况。
- 关于ThreadLocal内存泄漏引起的思考
- 关于ThreadLocal引起内存泄漏的理解
- 关于弹层引起的内存泄漏
- 关于ThreadLocal的思考
- ThreadLocal的内存泄漏问题
- ThreadLocal的内存泄漏问题
- ThreadLocal 导致的内存泄漏
- AfxBeginThread引起的内存泄漏
- ViewResolver引起的内存泄漏
- __bridge_retained 引起的内存泄漏
- ThreadLocal可能引起的内存泄露
- ThreadLocal可能引起的内存泄露
- ThreadLocal可能引起的内存泄露
- ThreadLocal可能引起的内存泄露
- 【并发】ThreadLocal可能引起的内存泄露
- ThreadLocal可能引起的内存泄露
- ThreadLocal的用法和内存泄漏
- 内存泄漏检测工具和Handler引起的内存泄漏问题
- 20161101 Python 读书笔记之八皇后问题
- struts2之ModelDriven
- 第二十一章 用于不相交集合的数据结构
- 链队列---队列的链式表示和实现
- Failed to read candidate component class错误分析
- 关于ThreadLocal内存泄漏引起的思考
- Android Studio导入项目慢
- 完美解决VS2010链接错误:LINK:fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
- C#中listview分页(动软代码生成器)
- 欢迎使用CSDN-markdown编辑器
- android图片加载哪家强
- c语言中的文件
- 类与对象的理解
- redis和memcache的区别