至今还没搞懂ThreadLocal怎么玩?
前言
ThreadLocal想必都不陌生,当多线程访问同一个共享变量时,就容易出现并发问题,为了保证线程安全,我们需要对共享变量进行同步加锁,但这又带来了性能消耗以及使用者的负担,那么有没有可能当我们创建一个共享变量时,每个线程对其访问的时候访问的都是自己线程的变量呢?没错那就是ThreadLocal。 ThreadLocal使用
举个简单例子: 比如实现一些数据运算的操作,过程中可能需要借助一个临时表去处理数据,临时表有一列存的每一次的执行ID,执行完成根据此次的执行ID进行删除临时表数据。可以使用一个ThreadLocal来存储当前线程的执行ID。(此处暂不考虑性能问题) @Service public class DataSyncServiceImpl { private final Logger logger = LoggerFactory.getLogger(DataSyncServiceImpl.class); private static final ThreadLocal execLocalId = ThreadLocal.withInitial(()->new String()); @Autowired private JdbcTemplate jdbcTemplate; /** * 借助临时表进行数据运算操作 * 临时表字段(id,execution_id) */ public void calculateData(String key){ try { execLocalId.set(UUID.randomUUID().toString()); calculate(); check(); System.out.println("同步数据..."); }finally { destory(); } } private void calculate(){ try { System.out.println("数据运算"); String execId = execLocalId.get(); //... Thread.sleep(1000L); } catch (InterruptedException e) { logger.error("执行异常!",e); } } private void check(){ try { System.out.println("数据运算"); String execId = execLocalId.get(); //... Thread.sleep(1000L); } catch (InterruptedException e) { logger.error("执行异常!",e); } } private void destory(){ //根据execution_id删除临时表数据 StringBuffer sql = new StringBuffer(); sql.append("delete from temp_table where execution_id = ?"); jdbcTemplate.update(sql.toString(),execLocalId.get()); execLocalId.remove(); } }
这样的话保证了每一个请求线程都有自己的执行ID,清除数据时互不影响。 ThreadLocal实现原理
进入Thread类,可以看到这样两个变量,threadLocals和inheritableThreadLocals,他们都是ThreadLocalMap类型,而ThreadLocalMap是一个类似Map的结构。默认情况下两个变量都为null,当前线程调用set或者get时才会创建。也就是说ThreadLocal变量其实是存在调用线程的内存空间中。 每个Thread线程都保存了一个共享变量的副本。threadLocals:当前线程的ThreadLocal变量inheritableThreadLocals:解决子线程不能访问父线程中的ThreadLocal变量
ThreadLocalMap
ThreadLocalMap是一个key为ThreadLocal本身,值为存入的value,对于不同的线程,每次获取副本时,别的线程不能获取到当前线程的副本值,形成了隔离。
Thread和ThreadLocal的关系
Set方法源码分析 /** * 返回当前线程中保存ThreadLocal的值 * 如果当前线程没有此ThreadLocal变量, * 则它会通过调用{@link #initialValue} 方法进行初始化值 * @return 返回当前线程对应此ThreadLocal的值 */ public T get() { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 如果此map存在 if (map != null) { // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e ThreadLocalMap.Entry e = map.getEntry(this); // 对e进行判空 if (e != null) { @SuppressWarnings("unchecked") // 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值 T result = (T)e.value; return result; } } /* 初始化 : 有两种情况有执行当前代码 第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象 第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry */ return setInitialValue(); } /** * 初始化 * @return the initial value 初始化后的值 */ private T setInitialValue() { // 调用initialValue获取初始化的值 // 此方法可以被子类重写, 如果不重写默认返回null T value = initialValue(); // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用map.set设置此实体entry map.set(this, value); else // 1)当前线程Thread 不存在ThreadLocalMap对象 // 2)则调用createMap进行ThreadLocalMap对象的初始化 // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中 createMap(t, value); // 返回设置的值value return value; }
执行步骤: 获取当前线程,根据当前线程获取到ThreadlocalMap,即threadLocals; 如果获取到的Map不为空,则设置value,key为调用此方法的ThreadLocal引用; 如果Map为空,则先调用createMap创建,再设置value。 Set方法源码分析 /** * 返回当前线程中保存ThreadLocal的值 * 如果当前线程没有此ThreadLocal变量, * 则它会通过调用{@link #initialValue} 方法进行初始化值 * @return 返回当前线程对应此ThreadLocal的值 */ public T get() { // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 如果此map存在 if (map != null) { // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e ThreadLocalMap.Entry e = map.getEntry(this); // 对e进行判空 if (e != null) { @SuppressWarnings("unchecked") // 获取存储实体 e 对应的 value值,即为我们想要的当前线程对应此ThreadLocal的值 T result = (T)e.value; return result; } } /* 初始化 : 有两种情况有执行当前代码 第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象 第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry */ return setInitialValue(); } /** * 初始化 * @return the initial value 初始化后的值 */ private T setInitialValue() { // 调用initialValue获取初始化的值 // 此方法可以被子类重写, 如果不重写默认返回null T value = initialValue(); // 获取当前线程对象 Thread t = Thread.currentThread(); // 获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); // 判断map是否存在 if (map != null) // 存在则调用map.set设置此实体entry map.set(this, value); else // 1)当前线程Thread 不存在ThreadLocalMap对象 // 2)则调用createMap进行ThreadLocalMap对象的初始化 // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中 createMap(t, value); // 返回设置的值value return value; }
执行步骤: 获取当前线程,获取此线程对象中维护的ThreadLocalMap对象; 如果Map不为空,则通过当前调用的ThreadLocal对象获取Entry; 判断Entry不为空,则直接返回value; Map或Entry为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为Key和Value创建一个新的Map。 内存泄漏
从ThreadLocal整体设计上我们可以看到,key持有ThreadLocal的弱引用,GC的时候会被回收,即Entry的key为null。但是当我们没有手动删除这个Entry或者线程一直运行的前提下,存在有强引用链 threadRef->currentThread->threadLocalMap->entry -> value ,value不会被回收,导致内存泄漏。
出现内存泄漏的情况:没有手动删除对应的Entry节点信息,value一直存在。ThreadLocal 对象使用完后,对应线程仍然在运行。
避免内存泄露:使用完ThreadLocal,调用其remove方法删除对应的Entry。对于第二种情况,因为使用了弱引用,当ThreadLocal 使用完后,key的引用就会为null,而在调用ThreadLocal 中的get()/set()方法时,当判断key为null时会将value置为null,这就就会在jvm下次GC时将对应的Entry对象回收,从而避免内存泄漏问题的出现。
总结
本文主要讲解了ThreadLocal的作用及基本用法,以及ThreadLocal的实现原理和基础方法,注意事项。最后,用ThreadLocal一定要记得用完remove!
昨日重现!火箭到了该雪藏卧龙凤雏塞拉斯和格林的时间了!北京时间12月18日,火箭在主场迎战开拓者,结果以95107大比分失利,遭遇主场两连败。如果看过比赛,就知道与比分不相符合的是,其实本场比赛前三节火箭已经以6585落后开拓者,第四
全面放开了,但快递怎么变成了慢递?自从全面放开,本来以为快递将会以最快的速度由卖家到达买家,没想到,快递居然越来越慢,还没有放开之前快,连顺丰都不能保证时效了。那么,是什么原因让快递变成慢递了呢?工作人员阳了今天取
烽台科技再获殊荣入选2023年度工业信息安全监测应急支撑单位近日,国家工业信息安全发展研究中心公布了2023年度工业信息安全监测应急支撑单位评选结果公示,烽台科技凭借扎实的技术研发实力完备的安全服务体系多年的安全支撑经验等优势,再次入选工业
快递查询软件,批量查询物流,统一查询全部单号的签收时间当你拥有多个快递单号的时候,如何查询物流,比如签收时间发出时间等怎么同一查询到呢?不知道怎么操作的宝贝们,下面一起来试试。所需工具安装一个快递批量查询高手快递单号若干操作步骤打开快
康洪撸起袖子加油干,为高标准高质量建设雄安新区添砖加瓦来源人民网人民网雄安12月17日电(王红)两个资料柜,一张长条桌,一张折叠床在雄安国际酒店项目指挥部,一间由彩钢板搭建的简易房就是康洪近段时间的办公室及宿舍。视频2021年4月,经
人和人之间,最舒服的关系三句话有人问与人交往,最大的痛苦根源是什么?有一个答案颇受很多人赞同,那就是自己期待太高,以为这段关系会按照预设的模式,按部就班地进行下去。可是,往往结果是出人意料,甚至可以说是猝不及防
永远不要有弱者心态,无论对谁偶尔示弱撒撒娇可以调节一下氛围,但是如果你真的把自己打造成弱者人设,那么就相当于把被任何人轻易拿捏的机会昭之天下。父母会以为你好的名义控制你,伴侣会以你不敢为理由践踏你的底线,孩子
父母不在了,尽量别管兄弟姊妹这3件事,这不是薄情,而是远见你好呀,我是你们的李姑娘,欢迎点击上方蓝色字体关注我,我们一起感悟人生!人这一生不得不面对的一个事实,那就是终究有一天我们会长大,我们的父母也会离开我们。父母给我们留在这个世界上最
端坐阳台阅读四季疫情三年了,我不敢远足,除了生活用品的需要我不得不走出家门外,平常我知爱端坐在那仅有一个平方的阳台上,仔细阅读楼外四季变化,尽情享受大自然周而复始的回馈,我感觉我拥有了一切。春,这
在灰烬中寻找光明西塞罗有言历史是时代的见证,真理的火炬,记忆的生命,生活的老师和古人的使者历史宛如一本厚厚的书,书中写下了一段动人的岁同和文化的传承,历史就像一条奔腾不息的河,河中流淌着时代的脉搏
任何事情,先以和为贵晚上,做了一个梦,大致是去了一个照相馆,并没有照相,而是坐在里面和朋友聊天,时间稍微有点长,结果最后被收了150元。当时我跟工作人员沟通我并没有拍照为什么要收我钱,你是否有收费标准