范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

至今还没搞懂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元。当时我跟工作人员沟通我并没有拍照为什么要收我钱,你是否有收费标准
别当所谓的老实人,要做聪明的老实人毛姆曾说过这样一句话心软和不好意思会害死自己,薄情与无情才是生存利器。生活在这个尔虞我诈的世界,我们每一个人都有着自己的生存法则。在纷纷扰扰的人际交往中,我们都有着各自的朋友和圈子最新可用修改运动步数详细教程微信运动可以记录每天运动的步数,已经受到越来越多人的喜欢。有的人为了霸榜甚至连自家的阿猫阿狗都用上了,可以说为了霸榜已经到了无所不用其极的地步。有的小伙伴就说了,步数有什么用呢?单OPPOFindN2重磅来袭,FindX5低至百元上演砸场闹剧除了OPPOFindN2之外,在OPPO的第一个纵向折叠产品OPPOFindN2Flip中,还使用了也将采用同一设计,但不同尺寸的铰链。这种独特的转轴设计,使您可以在OPPOFinANKER推出521超极充,融合45W充电器与5000mAh充电宝,一个顶两个超极充是ANKER在2017年推出的一种多功能充电产品,它融合了充电器移动电源两种功能,内置锂离子电芯与电源适配器,可作为移动电源外带使用,也可插入墙插作为充电器使用,一个超极充就颜值出众佩戴舒适的头戴电竞耳机达尔优A730体验相信很多喜欢电竞的朋友,都有一款电竞耳机,二毛虽然不玩电竞,但由于世界杯开赛在即,还是趁双十一的优惠期选购了一款佩戴非常舒服的电竞耳机达尔优A730。达尔优电竞头戴耳机A73027投资约500万澳元,蜂巢能源入股澳大利亚锂矿(文潘昱辰编辑马媛媛)12月8日,澳大利亚锂矿商STGEORGE在澳大利亚证券交易所披露信息,将与中国动力电池供应商蜂巢能源和签署非约束性谅解备忘录,建立战略合作关系。蜂巢能源将投新能源车企,谁是亏损王?小鹏卖一辆车亏8万,蔚来卖一辆车亏11万,理想卖一辆车亏2。3万?这样的说法自新势力企业开始卖车从未停息,如今愈演愈烈。卖的多亏的更多,成了当下新能源车企集体面临的困境。图图虫创意群晖docker部署bitwarden与iOS浏览器设置看了看别人的原创,发现我的bitwarden已经过时了,新的已经是vaultwarden了,要重新部署一次,记录一下过程与大家分享一下。bitwarden是什么bitwarden是守护家人健康,能去除PM2。5的油烟机你见过没米家智能净烟机P1头条创作挑战赛厨房居然是PM2。5重灾区俗话说民以食为天,对于国人来说,饮食不仅仅是满足我们生存的需要,几千年的历史传承更让其上升为一种艺术。特别是随着改革开放的成功,大家的物质生终于找到男孩为啥这么费妈的原因了,原来真的有科学依据美国曾有一项研究,对上万名家有男孩的父母进行监测。18年后,研究结果表明养男孩的父母比养女孩的父母,智力水平下降得更快。西班牙也有专家指出妈妈怀上男宝宝,会加速身体各项器官的衰老,明确了!幼儿园只能收取四类费用近日,湖北省发改委省教育厅省市场监管局发通知,进一步加强我省幼儿园收费管理工作。通知明年1月1日起执行。通知明确了,全省幼儿园收费项目实行省级管理,统一为保教费住宿费服务性收费和代