《框架封装 · 线程装饰器》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗
🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍


写在前面的话

线程池和异步操作属于日常开发中普遍场景,作为框架封装人员来说,如何保证好主线程和子线程之间的上下文一致,是框架一个基础且必备的能力。
本篇文章介绍一下如何使用线程装饰器实现跨线程信息传递,提供一个实战方案,希望能帮助到大家,顺便作为一个知识补充。


技术简介

框架搭建过程中,诸多功能封装都依赖于 ThreadLocal 类型变量。众所周知,ThreadLocal 里面的值,放眼在整个SpringMVC请求中,都可以拿到。但是多线程的时候,ThreadLocal将失效。
所谓多线程上下文复制,其实就是使用异步线程的时候,也能拿到主线程里面ThreadLocal的内容。
具体做法就是在声明线程池的时候,指定TaskDecorator,在装饰代码中复制 ThreadLocal 类型变量。


实现阶段

一、定义线程装饰器类
参考下面示例代码,步骤大概就这样:
Step1、主线程获取ThreadLocal变量;
Step2、将变量重新放入到子线程中;
Step3、执行原有线程逻辑;

public class ContextCopyingDecorator implements TaskDecorator {

    @Override
    public Runnable decorate(Runnable runnable) {
        long parentThreadId = Thread.currentThread().getId();
        Map<String, Object> map = new HashMap<>(8);
        Map<String, Object> parameterMap = OnelinkContextHolder.getCopiedMap();
        map.putAll(parameterMap);
        return () -> {
            long childThreadId = Thread.currentThread().getId();
            boolean isSameThread = Objects.equals(parentThreadId, childThreadId);
            if (!isSameThread) {
                OnelinkContextHolder.putValue(map);
            }
            try {
                runnable.run();
            }
        };
    }
}

二、线程池指定装饰器

@Bean("taskExecutor")
public Executor taskExecutor() {

  // 创建一个线程池
  ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
  
  // 这里省略其他线程池核心参数设定
  。。。
    
  // 设置任务装饰器
  executor.setTaskDecorator(new ContextCopyingDecorator());

  return executor;
}

三、使用效果
通过线程池操作的时候,子线程池已经可以拿到相关值了。


经验分享

通过上述的步骤,可以基本实现线程上下文的复制了,但业务开发人员往往不会按套路出牌,即不一定用我们封装好的线程池,各种自定义线程池、@Async异步等写法层出不穷,有什么办法替开发人员兜底呢?
那就是趁他们神不知鬼不觉,偷偷设置好!
可以针对框架进行额外处理,项目在初始化的时候,将所有线程池类的装饰器设置为框架默认的装饰器,代码如下:

public class ThreadPoolTaskExecutorBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof ThreadPoolTaskExecutor) {
            ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
            log.info("使用装饰线程池: {}", beanName);
            executor.setTaskDecorator(ContextCopyingDecorator.getInstance());
        }
        return bean;
    }
}

效果就是,只要使用了线程池,都会享受到框架线程装饰器的功能。

Tips:这里又用到了BeanPostProcessor接口,是Spring中定义的接口,可以在Spring容器的创建过程中,回调BeanPostProcessor中定义的两个方法,分别是在每一个bean对象的初始化方法调用之前和之后回调。


知识拓展

ThreadLocal使用完成后为什么要及时remove?
大家都知道ThreadLocal是实现线程封闭的一种方式,其原理是以线程为键单独维护每一个线程的值,ThreadLocal在当前线程存的值,只能被这个线程所使用。
那么,问题来了,大家知道,线程就是用来执行方法的,正常情况下,如果方法执行完了,线程对象就会被回收,但是如果在ThreadLoca使用后,并没有调用remove方法来清除这个对象,而一直保持着引用关系,那么从GC Roots可达性分析的角度来看,这个线程对象一直可达,那么就不可能被标记为垃圾对象,也就不可能被回收了。而执行完方法后线程其实已经不再使用,而这种通过ThreadLocal引用的线程对象会一直堆积,就会造成内存泄露。
所以,为了避免内存泄露,Thread Local每次取值使用过后一定要调用remove方法及时清理。

上下文工具类封装示例

public class ContextHolder {

    private static final ThreadLocal<Map<String, Object>> HOLDER = new ThreadLocal<>();

    public static Object getValue(String key) {
        Map<String, Object> holderMap = HOLDER.get();
        if (holderMap == null) {
            return null;
        }
        return holderMap.get(key);
    }

    public static Optional<Object> getOptional(String key) {
        Map<String, Object> holderMap = HOLDER.get();
        if (holderMap == null) {
            return Optional.empty();
        }
        return Optional.ofNullable(holderMap.get(key));
    }

    public static String getString(String key) {
        Object value = getValue(key);
        return value == null ? null : value.toString();
    }

    public static Integer getInteger(String key) {
        Object value = getValue(key);
        if (value == null) {
            return null;
        } else if (value instanceof Integer) {
            return (Integer) value;
        } else if (value instanceof String) {
            return Integer.valueOf(value.toString());
        }
        throw new NumberFormatException("Invalid integer value :" + value);
    }

    public static Boolean getBoolean(String key) {
        Object value = getValue(key);
        if (value == null) {
            return null;
        } else if (value instanceof Boolean) {
            return (Boolean) value;
        } else if (value instanceof String) {
            return Boolean.valueOf(value.toString());
        }
        throw new IllegalArgumentException("Invalid boolean value :" + value);
    }

    public static void putValue(Map<String, Object> paramMap) {
        if (paramMap == null || paramMap.isEmpty()) {
            return;
        }
        Map<String, Object> map = HOLDER.get();
        if (map == null) {
            // 创建一个新的map接受外部参数,避免数据在外部被修改
            HOLDER.set(new HashMap<>(paramMap));
        } else {
            map.putAll(paramMap);
        }
    }

    private static Object putValue(String key, Object value, boolean rewrite) {
        if (StrUtil.isBlank(key)) {
            return null;
        }
        Map<String, Object> holderMap = HOLDER.get();
        if (holderMap == null) {
            holderMap = new HashMap<>(1);
            HOLDER.set(holderMap);
        }
        return rewrite ? holderMap.put(key, value) : holderMap.putIfAbsent(key, value);
    }

    public static void putValue(String key, Object value) {
        putValue(key, value, true);
    }

    public static Object putIfAbsent(String key, String value) {
        return putValue(key, value, false);
    }

    public static void putIfNotNull(String key, Object value) {
        if (value == null) {
            return;
        }
        putValue(key, value);
    }

    public static void putIfNotBlank(String key, String value) {
        if (StrUtil.isBlank(value)) {
            return;
        }
        putValue(key, value);
    }

    public static Map<String, Object> getCopiedMap() {
        Map<String, Object> holderMap = HOLDER.get();
        return holderMap == null ? Collections.emptyMap() : new HashMap<>(holderMap);
    }

    public static Object remove(String key) {
        Map<String, Object> holderMap = HOLDER.get();
        if (holderMap == null) {
            return null;
        }
        return holderMap.remove(key);
    }

    public static void clear() {
        if (HOLDER.get() != null) {
            HOLDER.get().clear();
        }
    }
}

总结陈词

上文介绍了框架封装人员,如何处理线程上下文信息复制,仅供参考。
核心需要封装的成员有线程池、装饰器、上下文工具类,实际开发中,代码不止上述示例那样简单,能做的事情也更多,后续相关专栏再展开。
本系列博文将介绍框架搭建人员如何以恰当的方式应对各式各样的情况,这也是此专栏的主题。
💗 后续将持续更新,请多多支持!

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

战神刘玉栋

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值