一、如何“随时随地”获取线程局部变量?
最近有一个同事遇到这样一个需求:根据数据源配置信息不同,将数据批量入库到不同的数据库实例。
- 一共实现了AClass、BClass、CClass、DClass、ECalss这样五个类。
- 在AClass中基于配置获取数据库数据源配置,然后初始化了一个JdbcTemplate。
- 将JdbcTemplate对象当作函数参数在5个类函数之间传递,最终在ECalss中使用JdbcTemplate将数据批量入库。
因此我提出了一个问题,你看人家Spring的那个Session想在哪获取就在哪获取,没有到处传参吧?
ServletRequestAttributes servletRequestAttributes =(ServletRequestAttributes)RequestContextHolder.currentRequestAttributes();
HttpSession session = servletRequestAttributes.getRequest().getSession();
比如上面的这个代码在当前请求线程内,可以随时随地获取HttpSession,也不用把HttpSession当作函数参数到处传递啊?
二、方案一:ConcurrentHashMap
过了不一会同事给了我这样一种方案。使用全局静态Map存放JdbcTemplate,key为当前线程的线程名称(唯一性)。为了避免线程安全问题,同事使用了ConcurrentHashMap。
public static ConcurrentHashMap<String, JdbcTemplate> templates = new ConcurrentHashMap<>();
如上图所示,这个方案是可以实现需求的
-
ConcurrentHashMap templates
作为一个静态成员变量,可以随时随地被引用。 -
在AClass中根据数据源配置初始化JdbcTemplate,并将其放入Map。
templates.put(Thread.currentThread().getName(),jdbcTemplate);
-
用到jdbcTemplate的时候,随时从ConcurrentHashMap中取出来。
templates.get(Thread.currentThread().getName())
。
虽然可以实现需求但是不够好,最主要的原因是:多线程使用一个Map,为了保证线程安全,ConcurrentHashMap使用了synchronized,所以执行效率肯定下降。就像多个人要上厕所,就一个坑,肯定有人需要等着。
三、方案二:使用ThreadLocal
所以更好的方案是:
public static ThreadLocal<JdbcTemplate> templates = new ThreadLocal<>();
ThreadLocal通过set方法将变量放入一个ThreadLocalMap,每个线程一份有效避免多线程资源竞争(如下图)。
templates.set(jdbcTemplate); //向ThreadLocalMap中放入
templates.get(); //从ThreadLocalMap中取出
ThreadLocalMap是当前线程Thread的一个属性(在执行ThreadLocal#set
方法时被赋值),所以它的生命周期是和当前线程一样的,就不用我们自己去维护它的生命周期了,可以有效避免内存溢出问题。
当然如果你的线程一直执行不完,保存变量的ThreadLocalMap就一直不死,线程多了,数据大了,还是会内存溢出。