ThreadLocal的使用笔记

背景:在项目中有一个关于异步批量支付的功能,这个功能需要在前台立即返回信息,并且可以批量处理多笔数据,在实现这个业务需求时,我首先是根据支付通道的类型,判断调用不同的异步支付方法,开启新的线程调用相应的支付方法,在这个具体的支付方法中,按照支付的批次查询出需要进行操作的数据,对于这些数据,通过for循环调用对应的第三方支付的帮助客户端,就是在这个地方调用时需要做到同步操作,需要实时返回数据,去更新每一条锁定的数据的记录,此外有额外的定时器负责定时冲正扫描每个批次中的支付结果,若批次中每一笔都支付成功,就更新批次的支付结果。

问题:原本的设计是将第三方支付的客户端方法设定为静态方法,方便工具化调用,如下图所示:

ThreadLocal的使用笔记

支付的工具类

对于客户端的调用者来说,很多请求都是公共的,只需要传入必要的少数参数即可完成支付操作。原本这样的设计是没什么问题的,但现在调用第三方的请求接口时,会出现支付结果为未知,需再次查询的情况。处理逻辑如下图所示:

 

ThreadLocal的使用笔记

未知结果的处理逻辑

因此在处理调用第三方的接口后需要进行轮询操作查询结果。于是产生了如下所示的第一版代码:

 

ThreadLocal的使用笔记

第一版代码-调用

 

ThreadLocal的使用笔记

第一版代码-轮询操作

其实这是第二版的代码,第一版的代码中response对象是用statsic关键字和voliate关键字修饰的类变量,并且轮询判断是在方法体外,无需传入response变量进行递归调用;结果发现使用static关键字会导致多次执行出错,也就是在执行下一次操作前必须清空response的值,在多线程环境下,这种操作不靠谱,因此改成了如上图所示的第二版代码。在上图所示代码中,采用每次轮询前先创建一个空的对象,然后传入轮询的方法中,通过判断入参,决定是否继续回调自身。但这种方式比较不够优雅,这时我突然想到了多线程环境下常用的一个工具类ThreadLocal,结合它的使用,完成下述的第三版代码;

 

ThreadLocal的使用笔记

第三版代码-ThreadLocal对象

 

ThreadLocal的使用笔记

第三版代码-方法调用

 

ThreadLocal的使用笔记

第三版代码-轮询方法

那么我们来分析下,为什么同样是被static关键字修饰的,采用一般的对象就会导致问题,而ThreadLocal对象却不会出现问题呢;那么看一下ThreadLocal的源码:

 

ThreadLocal的使用笔记

ThreadLoacl-get方法

 

ThreadLocal的使用笔记

ThreadLocal-set方法

 

 

可以看到ThreadLocal中存储数据其实都是使用的内部的一个Map对象,并且是使用当前线程的名称作为map的key来存储数据(可能描述的不够准确,有兴趣的可以看下源码),因此可以保证多线程环境下,存储的数据互不干扰。而对于我实际项目中的使用情况,尽管对于所有的调用ThreadLoacl都是用的同一个,但他们内部存储的ThreadLocalMap对象却是不一样的,因此,数据并不会产生问题。

总结一下,在多线程的环境下,可以采用类似ThreadLoacl的方式为每个线程保存一份本地的数据,保证这些数据之间互不干扰,这样的使用,可以避免一些加锁的情况,因为锁的力度越大,系统的响应速度越慢,这样不利于系统的整体性能,并且锁的滥用会导致各种各样的问题,因此在多线程开发的环境下,尽量采用一些替代方案来避免锁的使用。