大家好,我是鲲哥。几天前,我们的线路发生了严重故障。这个故障是多线程使用不当造成的。很有代表性。对,所以分享给大家,希望能帮助大家避坑。问题简介首先简单介绍一下问题的背景。我们有返利业务,有搜索场景。这个关键词被转移到各个平台(如淘宝、京东、拼多多等)调整搜索界面,聚合这些搜索结果返回给用户。一开始这个搜索场景的处理是单线程的,但是随着接入平台越来越多,搜索请求耗时越来越长。由于各个平台的搜索请求都是独立的,显然单线程可以优化成多线程。在下面的案例中,搜索请求的耗时仅取决于搜索接口耗时最长的平台,所以使用多线程显然是对接口性能的极大优化,但是使用多线程之后改造上线,社区很多用户反映前台显示“APP需要升级提示”,定位后发现多线程无法获取客户端信息,由于缺少客户端信息,返回给用户需要升级的提示。伪代码如下//启用多线程处理newThread(newRunnable(){@Overridepublicvoidrun(){MapclientInfoMap=Context.getContext().getClientInfo();//无法获取客户端信息,返回需要升级的信息if(clientInfoMap==null){thrownewException("版本号太低,请升级版本");}Stringversion=clientInfoMap.get("version");//遵循正常逻辑....}}).start();画外音:生产中多线程使用线程池来实现。为了演示方便,直接newThread也是一样的效果,大家都懂的。那么问题来了,为什么改成多线程后获取不到客户端信息呢?要理解这个问题,首先要理解客户端信息是如何存储的。Threadlocal引入不同客户端请求的客户端信息(wifi或4G、型号、应用名称、电池等))明显不同。dubbo业务线程拿到client请求后,会先提取有用的请求信息(比如本文中的MapclientInfo),但是这个clientInfo可能会在线程调用的各种方法中用到,所以如何存储取决于它有成为真正的问题,相信有经验的朋友马上就会想到,没错,用Threadlocal!为什么使用它,它有什么优势?简单来说有两点:无锁提升并发性能简化变量的传递逻辑1.无锁提升并发性能先说第一点。无锁对并发性能的提升影响并发的原因有很多,其中最重要的一个原因就是锁。为了防止对共享变量的竞争,共享变量必须被锁定。如果竞争共享变量的线程数增多,显然会严重影响系统的并发性。最好的办法是使用“ShadowClone”为每个线程创建一个线程局部变量,这样就避免了对共享变量的竞争,实现了无锁无锁。ThreadLocal是一个线程局部变量,可以用来为每个线程创建一个线程局部变量,使用方法如下返回新的SimpleDateFormat("yyyy-MM-dd");}};publicStringformatDate(Datedate){returnthreadLocal1.get().format(date);}这样,每个线程都有一个与其他线程无关的SimpleDateFormat实例的独占副本,它们使用调用formatDate时的SimpleDateFormat实例也是自己唯一的副本,无论你如何操作副本,都不会影响其他线程。从上面的例子我们可以看出,可以使用newThreadLocal+initialValue为创建的ThreadLocal实例初始化局部变量(initialValue方法会在第一个get调用时调用,初始化局部变量)当然,如果后面需要修改局部变量,也可以通过如下方式修改threadLocal1.set(newSimpleDateFormat("yyyy-MM-dd")),使用threadLocal1.get()获取线程局部变量。有的朋友会很好奇线程局部变量是怎么存储的。一张图片胜过千言万语。每个线程(Thread)里面都有一个ThreadLocalMap。ThreadLocal的get和set操作实际上是在最底层的ThreadLocalMap上操作的。publicclassThreadimplementsRunnable{/*ThreadLocal值属于这个线程。该映射由ThreadLocal类维护*。*/ThreadLocal.ThreadLocalMapthreadLocals=null;}类似于HashMap,存储键值对,只是每一项(Entry)中的key是threadlocal变量(如上例中的threadLocal1),值为我们要存储的值(例如上面的SimpleDateFormat实例)。此外,它们在遇到哈希冲突时有不同的处理策略。HashMap遇到hash链表方法是用冲突的,而ThreadLocalMap使用的是线性检测方法2.简化变量的传递逻辑接下来我们看看使用ThreadLocal的两个好处,它简化了变量的传递逻辑。线程在处理业务逻辑的时候,可能会调用几十个方法,如果这些方法中只有少数几个需要用到clientInfo,那么在这几十个方法中定义一个clientInfo参数逐层传递显然不现实吗?那怎么办呢,用ThreadLocal来解决这个问题。从上面可以看出,ThreadLocal设置的局部变量是和threadlocal一起存放在Thread的ThreadLocalMap内部类中的,所以可以从线程调用的任何方法中获取。伪代码如下:publicclassThreadLocalWithUserContextimplementsRunnable{privatestaticThreadLocal