幼儿饰品瑜伽美体用品微软
投稿投诉
微软创意
爱情通信
用品婚姻
爱好看病
美体软件
影音星座
瑜伽周边
星座办公
饰品塑形
搞笑减肥
幼儿两性
智家潮品

多线程引发的惨案直接把年终给干没了

  前些日子我们线上出现了一个比较严重的故障,这个故障是多线程使用不当引起的,挺有代表性的,所以分享给大家,希望能帮大家避坑问题简述
  先简单介绍一下问题产生的背景,我们有个返利业务,其中有个搜索场景,这个场景是用户在app输入搜索关键词,然后server会根据这个关键词到各个平台(如淘宝,京东,拼多多等)调一下搜索接口,聚合这些搜索结果后再返回给用户,最开始这个搜索场景处理是单线程的,但随着接入的平台越来越多,搜索请求耗时也越来越长,由于每个平台的搜索请求都是独立的,很显然,单线程是可以优化为多线程的,如下
  img
  这样的话,搜索请求的耗时就只取决于搜索接口耗时最长的那个平台,所以使用多线程显然对接口性能是一个极大的优化,但使用多线程改造上线后,短时间内社群中有多名用户反馈前台展示APP需要升级的提示,经定位后发现是因为在多线程中无法获取客户端信息,由于客户端信息缺失,导致返回给用户需要升级的提示,伪代码如下开启多线程处理newThread(newRunnable(){Overridepublicvoidrun(){MapclientInfoMapContext。getContext()。getClientInfo();无法获取客户端信息,返回需要升级的信息if(clientInfoMapnull){thrownewException(版本号过低,请升级版本);}StringversionclientInfoMap。get(version);以下正常逻辑。。。。}})。start();
  画外音:在生产中多线程使用的是线程池来实现,这里为了方便演示,直接newThread,效果都一样,大家知道即可
  那么问题来了,改成多线程后客户端信息怎么就取不到了呢?要搞清楚这个问题,就得先了解客户端信息是如何存储的了Threadlocal简介
  不同客户端请求的客户端信息(wifi还是4G,机型,app名称,电量等)显然不一样,dubbo业务线程拿到客户端请求后首先会将有用的请求信息提取出来(如本文中的MapclientInfo),但这个clientInfo可能会在线程调用的各个方法中用到,于是如何存储就成为了一个现实的问题,相信有经验的朋友一下就想到了,没错,用Threadlocal!为什么用它,它有什么优势,简单来说有两点无锁化提升并发性能简化变量的传递逻辑1。无锁化提升并发性能
  先说第一个,无锁化提升并发性能,影响并发的原因有很多,其中一个很重要的原因就是锁,为了防止对共享变量的竞用,不得不对共享变量加锁
  如果对共享变量争用的线程数增多,显然会严重影响系统的并发度,最好的办法就是使用影分身术为每个线程都创建一个线程本地变量,这样就避免了对共享变量的竞用,也就实现了无锁化
  无锁化
  ThreadLocal即线程本地变量,它可以为每个线程创建一份线程本地变量,使用方法如下staticThreadLocalSimpleDateFormatthreadLocal1newThreadLocalSimpleDateFormat(){OverrideprotectedSimpleDateFormatinitialValue(){returnnewSimpleDateFormat(yyyyMMdd);}};publicStringformatDate(Datedate){returnthreadLocal1。get()。format(date);}
  这样的话每个线程就独享一份与其他线程无关的SimpleDateFormat实例副本,它们调用formatDate时使用的SimpleDateFormat实例也是自己独有的副本,无论对副本怎么操作对其他线程都互不影响
  通过以上例子我们可以看出,可以通过newThreadLocalinitialValue来为创建的ThreadLocal实例初始化本地变量(initialValue方法会在首次调用get时被调用以初始化本地变量)。当然,如果之后需要修改本地变量的话,也可以用以下方式来修改threadLocal1。set(newSimpleDateFormat(yyyyMMdd))
  而使用threadLocal1。get()这样的方法即可获得线程本地变量
  可能一些朋友会好奇线程本地变量是如何存储的,一图胜千言
  每一个线程(Thread)内部都有一个ThreadLocalMap,ThreadLocal的get和set操作其实在底层都是针对ThreadLocalMap进行操作的publicclassThreadimplementsRunnable{ThreadLocalvaluespertainingtothisthread。ThismapismaintainedbytheThreadLocalclass。ThreadLocal。ThreadLocalMapthreadL}
  它与HashMap类似,存储的都是键值对,只不过每一项(Entry)中的key为threadlocal变量(如上文案例中的threadLocal1),value才为我们要存储的值(如上文中的SimpleDateFormat实例),此外它们在碰到hash冲突时的处理策略也不同,HashMap在碰到hash冲突时采用的是链表法,而ThreadLocalMap采用的是线性探测法2。简化变量的传递逻辑
  接下来我们来看使用ThreadLocal的等二个好处,简化变量的传递逻辑,线程在处理业务逻辑时可能会调用几十个方法,如果这些方法中只有几个需要用到clientInfo,难道要在这几十个方法中定义一个clientInfo参数来层层传递吗,显然不现实。那该怎么办呢,使用ThreadLocal即可解决此问题。由上文可知通过ThreadLocal设置的本地变量是同threadlocal一起保存在Thread的ThreadLocalMap这个内部类中的,所以可在线程调用的任意方法中取出,伪代码如下publicclassThreadLocalWithUserContextimplementsRunnable{privatestaticThreadLocalMapString,StringthreadLocalnewThreadLocal();Overridepublicvoidrun(){clientInfo初始化MapString,StringclientIthreadLocal。set(clientInfo);test1();}publicvoidtest1(){test2();}publicvoidtest2(){testX();}。。。publicvoidtestX(){MapclientInfothreadLocal。get();}}
  中间定义的任何方法都无需为了传递clientInfo而定义一个额外的变量,代码优雅了不少
  由以上分析可知,使用ThreadLocal确实比较方便,在此我们先停下来思考一个问题:如果线程在调用过程中只用到一个clientInfo这样的信息,只定义一个ThreadLocal变量当然就够了,但实际上在使用过程中我们可能要传递多个类似clientInfo这样的信息(如userId,cookie,header),难道因此要定义多个ThreadLocal变量吗?
  这么做不是不可以,但不够优雅,更合适的做法是我们只定义一个ThreadLocal变量,变量存的是一个上下文对象,其他像clientInfo,userId,header等信息就作为此上下文对象的属性即可,代码如下publicfinalclassContext{privatestaticfinalThreadLocalContextLOCALnewThreadLocalContext(){protectedContextinitialValue(){returnnewContext();}};privateL用户uidprivateMapString,StringclientI客户端信息privateMapString,S请求头信息privateMapString,MapString,S请求cookiepublicstaticContextgetContext(){return(Context)LOCAL。get();}}
  这样的话我们可通过Context。getContext()。getXXX()的形式来获取线程所需的信息,通过这样的方式我们不仅避免了定义无数ThreadLocal变量的烦恼,而且还收拢了上下文信息的管理
  通过以上介绍相信大家也都知道了clientInfo其实是借由ThreadLocal存储的,认清了这个事实后那我们现在再回头看开头的生产问题:将单线程改成多线程后,为什么在新线程中就拿不到clientInfo了?问题剖析
  源码之下无秘密,我们查看一下源码来一探究竟,获取本地变量的值使用的是ThreadLocal。get方法,那就来看下这个方法publicclassThreadLocalT{publicTget(){1。先获取当前线程ThreadtThread。currentThread();2。再获取当前线程的ThreadLocalMapThreadLocalMapmapgetMap(t);if(map!null){ThreadLocalMap。Entryemap。getEntry(this);if(e!null){Tresult(T)e。}}returnsetInitialValue();}}
  可以看到get方法主要步骤如下首先需要获取当前线程其次获取当前线程的ThreadLocalMap进而再去获取相应的本地变量值如果没有的话则调用initiaValue方法来初始化本地变量
  由此可知当我们调用threadlocal。get时,会拿到当前线程的ThreadLocalMap,然后再去拿entry中的本地变量,而对多线程来说,新线程的ThreadLocalMap里面的东西本来就未做任何设置,是空的,拿不到线程本地变量也就合情合理了解决方案
  问题清楚了,那怎么解决呢,不难得知主要有两种方案
  1。我们之前是在新线程的执行方法中调用threadlocal。get方法,可以改成先从当前执行线程中调用threadlocal。get获得clientInfo,然后再把clientInfo传入新线程,伪代码如下先从当前线程的Context中获取clientInfoMapclientInfoMapContext。getContext()。getClientInfo();newThread(newRunnable(){Overridepublicvoidrun(){此时的clientInfoMap由于是在新线程创建前获取的,肯定是有值的StringversionclientInfoMap。get(version);以下正常逻辑。。。。}})。start();
  2。只需把ThreadLocal换成InheritableThreadLocal,如下publicfinalclassContext{privatestaticfinalInheritableThreadLocalContextLOCALnewInheritableThreadLocalContext(){protectedContextinitialValue(){returnnewContext();}};publicstaticContextgetContext(){return(Context)LOCAL。get();}}newThread(newRunnable(){Overridepublicvoidrun(){此时的clientInfo能正常获取到MapclientInfoContext。getContext()。getClientInfo();StringversionclientInfo。get(version);以下正常逻辑。。。。}})。start();
  为什么InheritableThreadLocal能有这么神奇,背后的原理是什么?
  由前文介绍我们得知,ThreadLocal变量最终是存在ThreadLocalMap中的,那么能否在创建新线程的时候,把当前线程的ThreadLocalMap复制给新线程的ThreadLocalMap呢,这样的话即便你从新线程中调用threadlocal。get也照样能获得对应的本地变量,和InheritableThreadLocal相关的底层干的就是这个事,我们先来瞧一瞧InheritableThreadLocal长啥样publicclassInheritableThreadLocalTextendsThreadLocalT{ThreadLocalMapgetMap(Threadt){returnt。inheritableThreadL}voidcreateMap(Threadt,TfirstValue){t。inheritableThreadLocalsnewThreadLocalMap(this,firstValue);}}
  由此可知InheritableThreadLocal其实是继承自ThreadLocal类的,此外我们在getMap和createMap这两个方法中也发现它的底层其实是用inheritableThreadLocals来存储的,而ThreadLocal用的是threadLocals变量存储的publicclassThreadimplementsRunnable{ThreadLocal实例的底层存储ThreadLocal。ThreadLocalMapthreadLinheritableThreadLocals实例的底层存储ThreadLocal。ThreadLocalMapinheritableThreadL}
  知道了这些,我们再来看下创建线程时涉及到的inheritableThreadLocals复制相关的关键代码如下:publicclassThreadimplementsRunnable{publicThread(){init(null,null,ThreadnextThreadNum(),0);}privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize){init(g,target,name,stackSize,null,true);}privatevoidinit(ThreadGroupg,Runnabletarget,Stringname,longstackSize,AccessControlContextacc,booleaninheritThreadLocals){。。。ThreadparentcurrentThread();if(inheritThreadLocalsparent。inheritableThreadLocals!null)将当前线程的inheritableThreadLocals复制给新创建线程的inheritableThreadLocalsthis。inheritableThreadLocalsThreadLocal。createInheritedMap(parent。inheritableThreadLocals);}}
  由此可知,在创建新线程时,在初始化时其实相关逻辑是帮我们干了复制inheritableThreadLocals的操作,至此真相大白总结
  看完本文,相信大家对Threadlocal与InheritableThreadLocal的使用及其底层原理的掌握已不存在疑问,这也提醒我们熟练地掌握一个组件或一项技术最好的方式还是熟读它的源码,毕竟源码之下无秘密,当我们使用到别人封装好的组件或类时,如果有兴趣也可以也看一下它的源码,以本文为例,其实我们工程中多处地方都使用了Context。getContext()。getClientInfo();这样的获取客户端信息的形式,用惯了导致在多线程环境下没有引起警惕,以致踩了坑。
  另外需要注意的是ThreadLocal使用不当可能导致内存泄漏,需要在线程结束后及时remove掉,这些技术细节不是本文重点,故而没有深入详解,有兴趣的大家可以去查阅相关资料来源:https:mp。weixin。qq。comsObXr7SR9TJC6aqTIOYlqA
  作者:坤哥

塔里伊森本场比赛22分10篮板,稳定输出,带着火箭队18分大大一赛季出战23场比赛,场均上场19。5分钟能够得到7。3分5。9篮板1。3助攻1。2抢断1。3盖帽,命中率46。2,三分命中率24。1,罚球命中率57。4。大二赛季出战……NBA球星破坏力有多大?湖人教练曾扣碎篮板,詹姆斯将篮球扣瘪NBA被誉为世界篮球的最高殿堂,这可不止说说而已,不论是技战术水平、球员能力和商业化程度,NBA都做到了职业篮球联赛的最好。而NBA的对抗强度,也是世界公认最高,每年不少球员都……辛巴辛选为直播电商行业发展提供支撑,推动直播电商高质量发展近年来,以直播带货为代表的新业态新模式迅速发展,在激活消费市场、拉动经济增长的同时,也创造了大量就业创业机会,为经济社会的新发展格局注入了新动能。作为头部直播电商企业辛选集团的……iOS端微信消息通知横幅引热议!便捷好用?这家手机厂商有话要要说数码相关的热搜常客都有哪些,可能大多数人的第一反应都会是苹果,毕竟价格上涨、配置升级、新增配色等话题都能莫名其妙就上了热搜,只能说苹果的行业关注度还是高的。另一个较为典型的……水乡渔村福建东山铜陵马鞍屿绝美风光滨海风情这里有蜿蜒的海岸线,蔚蓝的天空,碧绿的海水,水天相接处,飘着点点白帆。天空海水帆船浑然天成,交相辉映,幻化出一幅如梦如幻的奇美画卷,置身于此,仿佛来到了人间仙境。这里是东山铜陵……从吃得饱到吃得好!原来,太空上的ampampquot美食秘笈北京2022年4月16日美通社2021年10月16日,神舟十三号飞行乘组的三名航天员翟志刚、王亚平、叶光富搭乘载人飞船奔赴中国空间站。在太空出差超180天后,三位航天员在今日顺……余承东果然没吹牛!国家队正式出手,华为迎来新队友本文原创,请勿抄袭和搬运,违者必究华为开启了向软件业务的转型,原本在硬件领域有很大优势的华为,已经加大软件产业的部署,其中操作系统的打造就是转型的重要一环。华为分别打造了……四叶草剧场白字大王阿塔尔测评作者:NGA液态黑剧情里傻不拉几的火精灵在游戏里的技能还不错,毕竟也是地城一霸,最大的特色就是25的生命百分比伤害,A路线能够提供较多增伤,同时护卫的专精路线技能有了较大……小米开始在越南生产手机!低端制造业,穷途末路小米也到越南生产智能手机了,如下新闻截图,小米的投资约8000万美元。前一段时间,小米在印度被罚钱。网上有大V分析,这是印度人关门打狗。据说,由于印度一直是贸易大幅……老年人喝牛奶,选纯牛奶好还是鲜牛奶好?怎样喝更有营养?多看看随着年龄的增大,身体慢慢地衰老,皱纹变多、身高缩水,这些都是我们看得见的,而在我们看不见的地方,身体的营养元素也在悄悄的流失,就比如钙。钙,是人体维持生命活动的重要物质,……幸福到万家全员劝离婚?丈夫怯弱婆婆偏心,女主靠这一点出圈赵丽颖幸福到万家文罗小可一直以来都非常喜欢赵丽颖,最近追她的新剧《幸福到万家》不亦乐乎,女主角何幸福是一个勇敢又善良的好姑娘。她勤奋耐劳,热情仗义,从小就吃了无数的……被堵门要债1000万,倪虹洁笑道,一生中追她人最多的时候近日,倪虹洁笑谈离婚后被追债的悲惨经历。救命,怎么会这么惨又这么好笑。拍了一天的戏收工,还有人来家门口追债。20多岁的壮小伙子,堵着她的门,跟她要一个承诺还债……
特种兵式旅游为何能火?特种兵们这样说日行数万步,花最少的时间和费用,游览最多的景点。日前,这种特种兵式旅游火了。这些特种兵多为大学生群体,一般周五晚上或双休日清晨出发,打卡各大景点后,在周一早上赶回教室上课……信仰足球彻底推翻金元足球,但也把自己埋葬了!黎明还远吗?信仰是什么?中国足球给了一个最好的答案。昨天还说足协高层昨天还就剩下三个没被查,今早的新闻头条收到足协最大的官杜兆才也被查了。退休之前的最后一周没查。官至副部都快退休了,不为足……银河战士Prime复刻致谢名单没原版开发人员被指责前RetroStudios工程师ZoidKirsch,也是《银河战士Prime》原版的开发人员,在推特上表示,《银河战士Prime复刻版》的致谢名单中未包含完整原始游戏制作人员……属于魅族的新开端从魅族20系列正式开始魅族,一直有着一股属于自己的顽强精神。在手机行业迅速更新迭代的今天,魅族并没有选择随波逐流,而是基于对产品以及用户认真负责的态度,一步一个脚印地前进,这从两年磨一剑的魅族新旗舰……分享一个ChatGPT的使用体验,另送一份彩讯股份深度分析报这周末的会议,比上周末还猛,都在聊AI。这个周末不少人都在关注中国发展高层论坛,这个论坛规格还是很高的,咱领导层有代表参加,西方企业家有很多重磅人物也来了,包括了苹果的库……为什么越简单越时髦?为什么越简单越时髦近年来,越来越多的人追求简约、简单的生活方式和时尚风格。这一现象的背后,有多种原因和解释。首先,简约时尚符合人们对于生活品质的追求。在快节奏的现代……作文作文里常用的十五类排比句有需要的家长可以收藏保存01hr朋友朋友是快乐日子里的一把吉他,尽情地为你弹奏生活的愉悦;朋友是忧伤日子里的一股春风,轻轻地为你拂去心中的愁云。朋友是成功道路上的一位良师,热情地将你引向阳……正是春花烂漫时,一起来感受田庄镇这场醉美乡村自行车赛春风吹绿,万物复苏,田庄镇在栾官屯村举行了一场倡导低碳生活,感受乡村风光为主题的环村山地自行车绿色骑行赛,40多位选手参加了比赛。本次自行车大赛从栾官屯村委会出发,沿途经……2023建发厦门马拉松赛前三名出炉!3。5万人激情奔跑!来源:厦门广电今天上午7:10,2023建发厦门马拉松赛在厦门国际会展中心鸣枪开跑!3。5万人激情奔跑,现场燃爆啦!时隔三年之后,境外选手再次站到了国内最美马拉松赛……巴特勒高效33分关键中投热火胜骑士,米切尔42分热火119115骑士,骑士加兰德因伤缺阵。阿德巴约上来连进2球,米切尔飚进2记三分砍下8分。莫布里、史蒂文斯的三分球帮助骑士领先。巴特勒爆发得到10分,热火首节2726领……华尔街一直忽视的风险美国债务悬崖关于隔夜鲍威尔的发言,市场几乎全部聚焦于加息,却忽视了另一个重大的风险美国债务悬崖。美联储主席鲍威尔在国会听证会上发出警告,不要以为美联储可以保护美国经济,如果美国爆发债……如何看待很多人都不知道产后有很多风险和后遗症?说真的,在我成为正式的医学生之前,也并不知道产后女性还面临着很多问题和风险,虽然我也是女性。后来,我还专门询问了身边的朋友,无论男女,大部分都不清楚。这种结果真的很让人崩……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网