利用spring实现多线程 + 事务回滚

前言

利用线程池和CountDownLatch,多线程并发处理批量数据,实现多线程事务回滚,事务补偿。

//定义两计数器
private CountDownLatch begin,end;

begin设置为1,用于发布开始命令,如果需要开始,则begin.countdown

end用于记录任务的执行情况。begin.countdown后,需end.await,等待任务都执行完。

当begin.countdown开始执行任务后,在最后需end.countdown

当end.countdown减到为0后,则切换到主线程,继续开始往下执行

基于回调函数

实现更灵活的去配置各业务数据操作场景,即:暴露excute方法执行线程任务,执行的具体执行任务交给回调函数实现。

基于spring上下文中获取事务管理器
封装获取spring上下文工具类

ApplicationContextProvider

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;

/**
* @Author by mocar小师兄
* @DESC:  从已有的spring上下文取得已实例化的bean
*/
@Component
public class ApplicationContextProvider implements ApplicationContextAware {

   private static final Logger log = LoggerFactory.getLogger(ApplicationContextProvider.class);

   private static ApplicationContext applicationContext;

   /**
    * 设置spring上下文
    * @param applicationContext spring上下文
    * @throws BeansException
    */
   @Override
   public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
       log.info("spring上下文applicationContext正在初始化,application:{}" ,applicationContext);
       this.applicationContext = applicationContext;
       log.info("spring上下文applicationContext初始化完成!");
   }

   public static ApplicationContext getApplicationContext() {
       return applicationContext;

   }

   public static Object getBean(String name){
       if(applicationContext==null){
           log.warn("applicationContext是空的");
           return null;
       }
       return getApplicationContext().getBean(name);

   }

   public static <T> T getBean(Class<T> clazz){
       return getApplicationContext().getBean(clazz);

   }


}

封装的工具类

package com.example.javademo.transaction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.CollectionUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;

public class TransactionMultipartExecutor<T> {

    private static final Logger log = LoggerFactory.getLogger(TransactionMultipartExecutor.class);

    /**
     * 单个线程处理的数据量
     */
    private int singleCount;

    /**
     * 处理的总数据量
     */
    private int listSize;

    /**
     * 开启的线程数
     */
    private int runSize;

    /**
     * 操作的数据集
     */
    private List<T> list;

    /**
     * 计数器(拦截器)
     */
    private CountDownLatch begin, end;

    /**
     * 线程池
     */
    private ExecutorService executorService;

    /**
     * 是否存在异常
     */
    private AtomicReference<Boolean> isError = new AtomicReference<>(false);

    /**
     * 回调函数
     */
    private CallBack callBack;

    /**
     * 概率模拟报错
     */
    private Random random = new Random();

    /**
     * 事务管理器
     */
    private PlatformTransactionManager transactionManager;


    public void setCallBack(CallBack callBack) {
        this.callBack = callBack;
    }

    public TransactionMultipartExecutor(int singleCount, List<T> list) {
        if (singleCount <= 0 || CollectionUtils.isEmpty(list)){
            throw new RuntimeException("Illegal parameter");
        }
        //transactionManager = ContextLoader.getCurrentWebApplicationContext().getBean(PlatformTransactionManager.class);
        transactionManager = ApplicationContextProvider.getBean(PlatformTransactionManager.class);
        this.singleCount = singleCount;
        this.list = list;
        this.listSize = list.size();
        this.runSize = (this.listSize%this.singleCount)==0 ? this.listSize/this.singleCount : this.listSize/this.singleCount + 1;
    }


    public void excute() throws InterruptedException {
        // 创建固定线程数量的线程池
        executorService = Executors.newFixedThreadPool(runSize);
        begin = new CountDownLatch(1);
        end = new CountDownLatch(runSize);
        //创建线程
        int startIndex = 0;
        int endIndex = 0;
        List<T> newList = null;
        for (int i = 0; i < runSize; i++) {
            //计算每个线程对应的数据
            if (i < (runSize - 1)) {
                startIndex = i * singleCount;
                endIndex = (i + 1) * singleCount;
                newList = list.subList(startIndex, endIndex);
            } else {
                startIndex = i * singleCount;
                endIndex = listSize;
                newList = list.subList(startIndex, endIndex);
            }
            //创建线程类处理数据
            MyThread<T> myThread = new MyThread(newList, begin, end) {
                @Override
                public void method(List list) {
                    DefaultTransactionDefinition def = new DefaultTransactionDefinition();
                    def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
                    TransactionStatus status = transactionManager.getTransaction(def);
                    //具体执行逻辑交给回调函数
                    try {
                        callBack.method(list);
                        /*if (random.nextInt(2) == 1) {
                            throw new RuntimeException("模拟异常抛出错误回滚");
                        }*/
                        log.warn("多线程事务批量操作执行成功,线程名:{},操作成功数量:{}",Thread.currentThread().getName(), list.size());
                    } catch (Exception e) {
                        // 接收异常,处理异常
                        isError.set(true);
                        //e.printStackTrace();
                        log.error("多线程事务批量操作抛错,线程名:{},操作失败数量:{},报错信息:{},{}",Thread.currentThread().getName(),list.size(),e.toString(), e);
                    }
                    //计数器减一
                    end.countDown();
                    try {
                        //等待所有线程任务完成,监控是否有异常,有则统一回滚
                        //log.warn("等待所有任务执行完成,当前时间:{},当前end计数:{}", LocalDateTime.now(), end.getCount());
                        end.await();
                        //log.warn("完成所有任务,当前时间:{},当前end计数:{}", LocalDateTime.now(), end.getCount());
                        if (isError.get()) {
                            // 事务回滚
                            transactionManager.rollback(status);
                        } else {
                            //事务提交
                            transactionManager.commit(status);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //执行线程
            executorService.execute(myThread);
        }
        //计数器减一,开始执行任务  begin此时为0
        begin.countDown();//
        //等待任务全部执行完毕,变为0则任务全部完成
        end.await();
        //关闭线程池
        executorService.shutdown();
        //不抛错也是可以回滚的
        /*if (isError.get()) {
            // 主线程抛出自定义的异常
            throw new RuntimeException("主线程抛出模拟异常");
        }*/
    }

    //抽象线程类
    public abstract class MyThread<T> implements Runnable {
        //list:总数据分割后某线程负责执行的数据
        private List<T> list;
        private CountDownLatch begin, end;

        public MyThread(List<T> list, CountDownLatch begin, CountDownLatch end) {
            this.list = list;
            this.begin = begin;
            this.end = end;
        }

        @Override
        public void run() {
            try {
                begin.await();
                //执行程序
                method(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //计数器减一
                //end.countDown();
            }
        }

        public abstract void method(List<T> list);
    }

    //回调接口定义
    public interface CallBack<T> {
        public void method(List<T> list);
    }

    public static void main(String[] agrs) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add("hello" + i);
        }
        TransactionMultipartExecutor<String> tool = new TransactionMultipartExecutor(3, list);
        tool.setCallBack(new CallBack<String>() {
            @Override
            public void method(List<String> list) {
                //总数据分割后某线程负责执行的数据
                for (int i = 0; i < list.size(); i++) {
                    System.out.print(Thread.currentThread().getId() + ":" + list.get(i) + " ");
                }
                System.out.println();
            }
        });
        try {
            tool.excute();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,080评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,422评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,630评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,554评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,662评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,856评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,014评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,752评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,212评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,541评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,687评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,347评论 4 331
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,973评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,777评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,006评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,406评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,576评论 2 349

推荐阅读更多精彩内容