多线程引发的惨案直接把年终给干没了
前些日子我们线上出现了一个比较严重的故障,这个故障是多线程使用不当引起的,挺有代表性的,所以分享给大家,希望能帮大家避坑 问题简述
先简单介绍一下问题产生的背景,我们有个返利业务,其中有个搜索场景,这个场景是用户在 app 输入搜索关键词,然后 server 会根据这个关键词到各个平台(如淘宝,京东,拼多多等)调一下搜索接口,聚合这些搜索结果后再返回给用户,最开始这个搜索场景处理是单线程的,但随着接入的平台越来越多,搜索请求耗时也越来越长,由于每个平台的搜索请求都是独立的,很显然,单线程是可以优化为多线程的,如下
img
这样的话,搜索请求的耗时就只取决于搜索接口耗时最长的那个平台,所以使用多线程显然对接口性能是一个极大的优化,但使用多线程改造上线后,短时间内社群中有多名用户反馈前台展示「APP 需要升级的提示」,经定位后发现是因为在多线程中无法获取客户端信息,由于客户端信息缺失,导致返回给用户需要升级的提示,伪代码如下 // 开启多线程处理 new Thread(new Runnable() { @Override public void run() { Map clientInfoMap = Context.getContext().getClientInfo(); // 无法获取客户端信息,返回需要升级的信息 if (clientInfoMap == null) { throw new Exception("版本号过低,请升级版本"); } String version = clientInfoMap.get("version"); // 以下正常逻辑 .... } }).start();
画外音 :在生产中多线程使用的是线程池来实现,这里为了方便演示,直接 new Thread,效果都一样,大家知道即可
那么问题来了,改成多线程后客户端信息怎么就取不到了呢?要搞清楚这个问题,就得先了解客户端信息是如何存储的了 Threadlocal 简介
不同客户端请求的客户端信息(wifi 还是 4G,机型,app名称,电量等)显然不一样,dubbo 业务线程拿到客户端请求后首先会将有用的请求信息提取出来(如本文中的 Map clientInfo),但这个 clientInfo 可能会在线程调用的各个方法中用到,于是如何存储就成为了一个现实的问题,相信有经验的朋友一下就想到了,没错,用 Threadlocal !为什么用它,它有什么优势,简单来说有两点 无锁化提升并发性能 简化变量的传递逻辑 1.无锁化提升并发性能
先说第一个,无锁化提升并发性能,影响并发的原因有很多,其中一个很重要的原因就是锁,为了防止对共享变量的竞用,不得不对共享变量加锁
如果对共享变量争用的线程数增多,显然会严重影响系统的并发度,最好的办法就是使用"影分身术"为每个线程都创建一个线程本地变量,这样就避免了对共享变量的竞用,也就实现了无锁化
无锁化
ThreadLocal 即线程本地变量,它可以为每个线程创建一份线程本地变量,使用方法如下 static ThreadLocal threadLocal1 = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd"); } }; public String formatDate(Date date) { return threadLocal1.get().format(date); }
这样的话每个线程就独享一份与其他线程无关的 SimpleDateFormat 实例副本,它们调用 formatDate 时使用的 SimpleDateFormat 实例也是自己独有的副本,无论对副本怎么操作对其他线程都互不影响
通过以上例子我们可以看出,可以通过 new ThreadLocal + initialValue 来为创建的 ThreadLocal 实例初始化本地变量(initialValue 方法会在首次调用 get 时被调用以初始化本地变量)。当然,如果之后需要修改本地变量的话,也可以用以下方式来修改 threadLocal1.set(new SimpleDateFormat("yyyy-MM-dd"))
而使用 threadLocal1.get() 这样的方法即可获得线程本地变量
可能一些朋友会好奇线程本地变量是如何存储的,一图胜千言
每一个线程(Thread)内部都有一个 ThreadLocalMap, ThreadLocal 的 get 和 set 操作其实在底层都是针对 ThreadLocalMap 进行操作的 public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; }
它与 HashMap 类似,存储的都是键值对,只不过每一项(Entry)中的 key 为 threadlocal 变量(如上文案例中的 threadLocal1),value 才为我们要存储的值(如上文中的 SimpleDateFormat 实例),此外它们在碰到 hash 冲突时的处理策略也不同,HashMap 在碰到 hash 冲突时采用的是链表法,而 ThreadLocalMap 采用的是线性探测法 2.简化变量的传递逻辑
接下来我们来看使用 ThreadLocal 的等二个好处,简化变量的传递逻辑 ,线程在处理业务逻辑时可能会调用几十个方法,如果这些方法中只有几个需要用到 clientInfo,难道要在这几十个方法中定义一个 clientInfo 参数来层层传递吗,显然不现实。那该怎么办呢,使用 ThreadLocal 即可解决此问题。由上文可知通过 ThreadLocal 设置的本地变量是同 threadlocal 一起保存在 Thread 的 ThreadLocalMap 这个内部类中的,所以可在线程调用的任意方法中取出,伪代码如下 public class ThreadLocalWithUserContext implements Runnable { private static ThreadLocal
当一个人这样回复你,心里不会拒绝你的爱爱情是非常珍贵的,在很多时候我们一定要知道该如何去考虑不要总是想太多,有些时候就是这样子的,你想的太多是没有用的也不一定能够解决问题,所以有些事我们要明白。男女之间有时候看起来没有
天下文章艾在人间自拍(冬日暖阳)天下是所有人的天下,非一己或一撮人的天下。文章虽表达的是个人的心声,但输出的却不应仅仅是个人的三观(价值观,道德观与人生观)。真的文章,敢于正视残酷的现实,
6种生命形态,碳基生命仅排第三,排名第一的硅基生命有多可怕?综述关于宇宙中所生存的生命形态,你了解几种?生命,是一个近乎奇迹般的存在。由不同物质所产生反应,地球在漫长的时间进化中出现了原始的生命。以碳元素为基础,地球上诞生了多种多样的碳基生
真的有外星生命吗外星生命,指存在于地球以外的生命体。这个概念囊括了简单的细菌到具有高度智慧的外星人。研究和测试关于外星生命猜想的学科被称作地外生物学或天体生物学(从天文视角研究地球生命也属于天体生
新发现两颗超级地球!距我们仅15。8光年,唯一担心的是早有生命既然太阳拥有行星,那宇宙中其他的恒星也很可能拥有自己的行星,虽然这是一种很合理的推测,但想要找到其他恒星的行星却不容易,如果将一颗恒星比作一座闪亮的灯塔,那它的行星就大概是灯塔下的
生命本就脆弱和不完美生命本就脆弱和不完美,我们生于尘埃,终归于尘埃。我们拥有的和我们缺失的,令我欢喜的,令我们无奈的,都是生命本质。美丽财富就智慧年老成熟和孩子气都是正当的,而且这就是世界。所有我们身
人到中年不得不苟且,流浪的独孤老狼书写平凡中的不平凡为什么叫自己流浪的独孤老狼而不是流浪的孤独老狼?从孤独到独孤,不单是语序和上口,境界气势立现。孤独是longly,独孤是along孤独是自怨自艾,独孤是我自横刀孤独是为人人为我,独
付过千般爱,莫生千般恨情感点评大赏一个人分手后的三个十年再三次被曾经的恋人伤害,那决不是愚蠢愚昧无知,而是病入膏肓无可救药。世间有一通百病的药方,却难寻千方能治的情伤。天意缘来缘尽就让它灰飞烟灭,付出千
浴冰重生来自一个标本的自白传说,凤凰是人世间幸福的使者。每五百年,它就要背负着积累于人世间的所有不快和仇恨恩怨,投身于熊熊烈火中自焚,以生命的美丽终结换取人世的祥和与幸福。在肉体经受了巨大的痛苦和轮回后它们
故乡的年味(一)娱兔迎春曾经仗剑走天涯,去看一看世界的繁华不知不觉离开家乡已经多年,从青葱岁月的求学时代开始,当时觉得很开心离开家乡,一直以为自己会成功,头也不回便离开了故乡。当时的心情就觉得未来
连续7天超1亿元!这里的免税店,又火了!12月初,海南省启动了为期两个月的海南离岛免税跨年狂欢季,通过抽奖打折满减等方式,全面促消费。全球精品(海口)免税城有限公司总经理助理刘加我们联合航司机场渠道合作伙伴,向广大旅客发