ThreadLocal线程池实例

本文介绍了一个基于多线程实现的索引任务处理流程,通过将不同类型的区块分配给独立线程进行处理,实现了高效的数据索引。文章详细解释了如何启动线程、检查线程状态以及整合索引结果。

学习了多线程的理论后,下面我们来看工作中遇到的一个具体实例。

 

 

    public Integer startAreaIndex(List<Long> activityIds) {
        //获取所有常规频道的区块,这里得到的区块为4个,所以最终会启用4个线程
        List<Area> areaList = areaServiceStub.queryAreaListByActivityIds(activityIds);
        LogTypeEnum.ACTIVITY_SOLR.info("solr开始索引,场频频道活动下的区块list:{}", areaList);
        if (CollectionUtils.isEmpty(areaList)) {
            return -1;
        }
        LogTypeEnum.ACTIVITY_SOLR.info("-----------------solr常规频道全量索引开始-----------------");
        //一个区块执行一个线程
        Iterator<Area> areaIterator = areaList.iterator();
        while (areaIterator.hasNext()) {
            Area area = areaIterator.next();
                AreaApplyTask areaApplyTask = new AreaApplyTask();
                areaApplyTask.setArea(area);
                areaApplyTask.setSolrWareService(solrWareService); //从这里注入的话,AreaApplyTask 就不用实现Spring的接口了
                areaApplyTask.setSolrActivityService(solrActivityService);
                areaApplyTask.setSolrPromoService(solrPromoService);
                solrIndexTaskExecutor.execute(areaApplyTask);
        }
        try{
            Thread.sleep(1000);   //这里先睡眠1秒,防止创建线程的时间长,导致下面第一次取活跃线程数为0,然后就直接break了
        }catch (Exception e){
            LogTypeEnum.ACTIVITY_SOLR.error("Solr线程索引失败", e);
        }
        for (; ; ) {
            int count = solrIndexTaskExecutor.getActiveCount();
            LogTypeEnum.ACTIVITY_SOLR.info("Solr Index Active Threads : " + count);
            try {
                Thread.sleep(1000);  //每隔1秒判断一次活跃线程数
            } catch (InterruptedException e) {
                LogTypeEnum.ACTIVITY_SOLR.error("Solr线程索引失败", e);
                return -1;
            }
            if (count == 0) {
                break;
            }
        }
        return 0;
    }

 

   AreaApplyTask类:

 

   

public class AreaApplyTask implements Runnable{

    private Area area;
    private WareService solrWareService;
    private PromoService solrPromoService;
    private ActivityService solrActivityService;

    public void run() {
        Area area = getArea();
        if(area != null && area.getType() != null){
            Integer areaType = area.getType();
            if(areaType.equals(AreaType.PROMO.getType())){  //促销区块
                //促销
                solrPromoService.indexByArea(area);
            }else if(areaType.equals(AreaType.WARE.getType())){
                //商品
                solrWareService.indexByArea(area);
            }else if(areaType.equals(AreaType.SHOP.getType())){
                //活动
                solrActivityService.indexByArea(area);
            }else if(AreaType.BRAND.isEqual(areaType)){
                //活动
                solrActivityService.indexByArea(area);
                //促销
                solrPromoService.indexByArea(area);
            }
        }
    }

    public Area getArea() {
        return area;
    }

    public void setArea(Area area) {
        this.area = area;
    }

    public void setSolrWareService(WareService solrWareService) {
        this.solrWareService = solrWareService;
    }

    public void setSolrPromoService(PromoService solrPromoService) {
        this.solrPromoService = solrPromoService;
    }

    public void setSolrActivityService(ActivityService solrActivityService) {
        this.solrActivityService = solrActivityService;
    }
}

 

    

public long indexByArea(Area area){
        /*
           这里为了方便多台机器可以同时跑,而且时间可以从页面传过来,采用了redis的方式,这里
           key为money,value的值在页面上设的为需要重刷solr的批次日期,例如:2016-12-07 00:00:00|2016-12-08 00:00:00|
           这里表示需要重新刷solr的批次日期为12月7日和8日,第一个机器执行后,第二个机器就会跑第二个时间
         */

        String key ="money";
        String time = activityUtilService.getRedisKeyValue(key);
        String beginTime = time.substring(0,time.indexOf("|"));
        String nextBeginTime = time.substring(time.indexOf("|")+1);
        activityUtilService.setInfoToRedisKey(key,1000*60*60,nextBeginTime);

        Batch batch = new Batch();
        batch.setActivityId((long)158001);
        batch.setBeginTime(DateUtil.createDate(beginTime, "yyyy-MM-dd HH:mm:ss"));
        batch.setEndTime(DateUtil.addDay(batch.getBeginTime(),1));
        List<Batch> batchList = batchServiceStub.getBatchTimeLineRangeList(batch);
        List<Long> batchIdList = new ArrayList<Long>();
        for(Batch batch1:batchList){
            batchIdList.add(batch1.getId());
        }
        Long areaId = area.getId();
        ApplyWareParam applyWareParam = new ApplyWareParam();
        applyWareParam.setBatchIdList(batchIdList);
        applyWareParam.setStatus(0);
        List<ApplyWare> applyWareList = applyWareServiceStub.getApplyWareListByBatchIds(applyWareParam, 1, 10000);
        LogTypeEnum.ACTIVITY_SOLR.warn("applyWareList=="+applyWareList.size()+",areaId="+area.getId());
        if(CollectionUtils.isEmpty(applyWareList)){
            return -1L;
        }
        ApplyPromoInfo applyPromoInfoParam = new ApplyPromoInfo();
        applyPromoInfoParam.setAreaId(areaId);
        //查出该区块下的所有促销提报列表
        List<ApplyPromoInfo> applyPromoInfoList = applyPromoInfoDao.queryForList(applyPromoInfoParam,1,Integer.MAX_VALUE);

        //把applyPromoInfoList中的促销ID合并到applyWareList中
        mergePromoListToWareList(applyWareList,applyPromoInfoList);

        return index(applyWareList);
    }

 

 

### ThreadLocal线程池中使用时需要注意的问题 #### 数据污染问题 在线程池环境中,线程会被多个任务重复使用。如果某个任务设置了 `ThreadLocal` 变量,而后续任务未进行清理或重新设置,可能会访问到之前任务遗留的数据。这种数据污染可能导致业务逻辑错误,尤其是在处理敏感上下文信息时[^2]。 ```java ExecutorService executor = Executors.newFixedThreadPool(5); executor.submit(() -> { try { threadLocal.set("some value"); // 执行业务逻辑 } finally { threadLocal.remove(); // 必须清理 } }); ``` 上述代码中,通过在 `finally` 块中调用 `remove()` 方法,确保即使任务执行过程中发生异常,也能及时清理 `ThreadLocal` 中的数据,避免污染后续任务[^2]。 #### 内存泄漏问题 `ThreadLocal` 的键是弱引用(`WeakReference`),当外部不再持有 `ThreadLocal` 实例的强引用时,垃圾回收器可以回收该键。然而,如果线程池中的线程长时间运行,且未主动调用 `remove()` 方法,可能导致 `ThreadLocalMap` 中残留的值无法被回收,从而引发内存泄漏[^4]。 线程池中的线程不会像 `new Thread` 那样在任务完成后销毁,因此 `ThreadLocal` 变量可能长期存在。如果未正确清理,可能导致线程中保留大量无用的 `ThreadLocal` 值,造成资源浪费[^1]。 #### 线程上下文传递问题 使用 `InheritableThreadLocal` 可以将父线程的 `ThreadLocal` 值传递给子线程,但在使用线程池时,线程并非真正“继承”自提交任务的线程,而是从线程池中获取的已有线程。因此,`InheritableThreadLocal` 无法正常传递上下文信息,需要额外的封装或使用 `ThreadFactory` 自定义线程创建逻辑[^2]。 #### 合理使用建议 为了避免数据污染和内存泄漏,建议在使用线程池时: - 每次任务执行完毕后,显式调用 `threadLocal.remove()` 清理数据,确保线程复用时不会保留旧值。 - 使用封装工具类或 AOP 拦截器,在任务执行前后自动管理 `ThreadLocal` 的生命周期。 - 对于需要跨线程传递上下文的场景,可以考虑使用 `TransmittableThreadLocal` 或结合 `CompletableFuture` 的 `async` 方法进行上下文传播。 #### 示例代码 ```java ThreadLocal<String> threadLocal = new ThreadLocal<>(); ExecutorService executor = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { int finalI = i; executor.submit(() -> { try { threadLocal.set("Value-" + finalI); System.out.println(threadLocal.get()); } finally { threadLocal.remove(); // 清理 ThreadLocal 避免内存泄漏 } }); } ``` 该示例展示了在任务执行完成后调用 `remove()` 方法,确保线程复用时不会保留上一个任务的 `ThreadLocal` 数据。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值