多线程任务得事务控制


前言

项目中有大数据进行存储得需求,考虑得性能问题,打算使用线程池得方式。但是多线程得情况,一张主表对应多张从表,其中一张表保存出错,怎么保证数据一致性,这就需要考虑多线程得事务控制,要么全部提交,要么全部回滚


一、springBoot得注解@Transactional(rollbackFor = Exception.class)

这个注解在多线程得情况下是不生效得,而且代码中使用得try/catch并且不抛出新的异常,也会导致其失效
参考链接:

二、使用线程计数器CountDownLatch

1.了解CountDownLatch类

CountDownLatch是一个同步工具类,它使用给定的 count初始化, await()方法会一直阻塞,直到计数器的值变为零(由于 countDown()方法被调用导致的),这时会释放所有等待的线程,且之后再调用 await()方法会直接返回,不会阻塞。 CountDownLatch是一个 一次性的类,计数器不能被重置,这一点与 CyclicBarrier不同。另一个不同点是: CountDownLatch是让所有线程 等待计数器的值变为零再继续执行;而 CyclicBarrier是要 等待指定个数的线程到达 Barrier 的位置再一起继续执行。
参考链接

实现思路

@Override
    public ResponseVo<String> saveAppMessageForOther(AppMessageForOtherVo appMessageForOtherVo) throws ExecutionException, InterruptedException {
        if(appMessageForOtherVo.getMsgList().size()>pageSum){
            return ResponseVo.failVo(GpBusinessCause.ILLEGAL_FORMAT, "数据不能大于1000条");
        }
        //校验第三方推松消息的是否符合配置的主题
        AppMessageType appMessageType = appMessageTypeDao.selectOneBySource(appMessageForOtherVo.getTypeSource(),appMessageForOtherVo.getTypeCode());
        if (appMessageType == null) {
            return ResponseVo.failVo(GpBusinessCause.TYPE_IS_NOT, GpBusinessCause.TYPE_IS_NOT_MSG);
        }
        List<AppMessageForSendVo> msgList = appMessageForOtherVo.getMsgList();
        //数据分割
        List<List<AppMessageForSendVo>> partition = ListUtils.partition(msgList, pageSize);

        //主线程控制器
        CountDownLatch mainThreadLatch = new CountDownLatch(partition.size());
        List<CompletableFuture<ResponseVo<String>>> list = new ArrayList<>();
        for (List<AppMessageForSendVo> msg : partition) {
            CompletableFuture<ResponseVo<String>> result = CompletableFuture.supplyAsync(() -> {
                AtomicBoolean rollbackFlag = new AtomicBoolean(false);
                // 设置一个事务
                DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); // 事物隔离级别,开启新事务,这样会比较安全些。
                TransactionStatus status = transactionManager.getTransaction(def); // 获得事务状态
                StringBuilder message = new StringBuilder();
                try {
                    // 业务处理开始
                    msg.forEach(i -> {
                        try{

                            AppMessage appMessage = new AppMessage();
                            BeanUtils.copyProperties(i, appMessage);
                            appMessage.setPublishDate(DateUtil.formatDate(i.getPublishDate(),DateUtil.FORMAT_DATETIME));
                            appMessage.setMsgTypeId(appMessageType.getId());
                            //保存消息详情表
                            if(CONTENTTYPE.equals(i.getContentType())){
                                appMessage.setContentId(saveContent(i.getContent()));
                            }
                            appMessage.setDeleted(false);
                            appMessageDao.insert(appMessage);
                            //保存发送用户关系表
                            String[] userIds = i.getUserIds();
                            saveAppMessageUser(userIds, appMessage.getId());
                            //保存发送平台关系表
                            saveTarget(appMessage.getSendPlatform(), appMessage.getId());
                        }catch (Exception e){
                            log.info(e.getMessage(),e);
                            message.append("id为:")
                                    .append(i.getOtherMsgId()).append("因")
                                    .append(e.getMessage())
                                    .append("保存失败!请重新推松这组数据id:");

                            // 如果出错了 本线程进行回滚
                            rollbackFlag.set(true);
                        }
                    });
                    if (rollbackFlag.get()) {
                        msg.forEach(j->{
                            message.append(j.getOtherMsgId()).append(",");
                        });
                        message.append("的数据");
                        return ResponseVo.failVo(GpBusinessCause.BUSSINESS_CHECK_FAIL, message.toString());
                    }else{
                        return ResponseVo.successVoWithMsg("保存成功!");
                    }
                } finally {
                    //主线程减一
                    mainThreadLatch.countDown();
                    //手动设置提交和回滚
                    if (rollbackFlag.get()) {
                        transactionManager.rollback(status);
                    } else {
                        transactionManager.commit(status);
                    }
                }
            }, executor);
            list.add(result);
        }
        return doIt(mainThreadLatch,list);
    }

    /**
     *
     * @param mainThreadLatch
     * @param list
     * @return
     * @throws ExecutionException
     * @throws InterruptedException
     */
    public ResponseVo<String> doIt(CountDownLatch mainThreadLatch,List<CompletableFuture<ResponseVo<String>>> list) throws ExecutionException, InterruptedException {
        StringBuilder msg = new StringBuilder();
        boolean falg = false;
        try {
            //主线程等待
            mainThreadLatch.await();
        } catch (InterruptedException e) {
            log.info(e.getMessage(),e);
            Thread.currentThread().interrupt();
        } finally {
            for (CompletableFuture<ResponseVo<String>> f : list) {
                ResponseVo<String> stringResponseVo = f.get();
                if (GpBusinessCause.BUSSINESS_CHECK_FAIL.equals(stringResponseVo.getCode())) {
                    falg = true;
                    msg.append(stringResponseVo.getMsg()).append("<------>");
                }
            }
        }
        return falg == false ? ResponseVo.successVoWithMsg("保存成功!") : ResponseVo.failVo(GpBusinessCause.BUSSINESS_CHECK_FAIL,msg.toString());
    }

由于我使用得是线程池,创建多个线程,线程最大数量给得10 ,那么为什么在创建主线程控制器,给5呢
首先要知道线程池在什么情况下才回去创建新得线程:
当线程数量与核心数量一致的时候。并不是当任务来了就直接创建一个新的线程去执行,而是先放到缓冲队列中,队列满的时候才会去判断最大线程数 从而决定是执行拒绝策略还是创建新的线程
参考链接

通过测试可以在保存出错得时候进行回滚,没有出错则正常保存,如有不对,请留言指教!

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值