利用spring實(shí)現(xiàn)多線程 + 事務(wù)回滾

前言

利用線程池和CountDownLatch,多線程并發(fā)處理批量數(shù)據(jù)捧弃,實(shí)現(xiàn)多線程事務(wù)回滾拿诸,事務(wù)補(bǔ)償跟压。

//定義兩計(jì)數(shù)器
private CountDownLatch begin,end;

begin設(shè)置為1胰蝠,用于發(fā)布開始命令,如果需要開始震蒋,則begin.countdown

end用于記錄任務(wù)的執(zhí)行情況茸塞。begin.countdown后,需end.await查剖,等待任務(wù)都執(zhí)行完钾虐。

當(dāng)begin.countdown開始執(zhí)行任務(wù)后,在最后需end.countdown

當(dāng)end.countdown減到為0后笋庄,則切換到主線程效扫,繼續(xù)開始往下執(zhí)行

基于回調(diào)函數(shù)

實(shí)現(xiàn)更靈活的去配置各業(yè)務(wù)數(shù)據(jù)操作場(chǎng)景,即:暴露excute方法執(zhí)行線程任務(wù),執(zhí)行的具體執(zhí)行任務(wù)交給回調(diào)函數(shù)實(shí)現(xiàn)直砂。

基于spring上下文中獲取事務(wù)管理器
封裝獲取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上下文取得已實(shí)例化的bean
*/
@Component
public class ApplicationContextProvider implements ApplicationContextAware {

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

   private static ApplicationContext applicationContext;

   /**
    * 設(shè)置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);

    /**
     * 單個(gè)線程處理的數(shù)據(jù)量
     */
    private int singleCount;

    /**
     * 處理的總數(shù)據(jù)量
     */
    private int listSize;

    /**
     * 開啟的線程數(shù)
     */
    private int runSize;

    /**
     * 操作的數(shù)據(jù)集
     */
    private List<T> list;

    /**
     * 計(jì)數(shù)器(攔截器)
     */
    private CountDownLatch begin, end;

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

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

    /**
     * 回調(diào)函數(shù)
     */
    private CallBack callBack;

    /**
     * 概率模擬報(bào)錯(cuò)
     */
    private Random random = new Random();

    /**
     * 事務(wù)管理器
     */
    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 {
        // 創(chuàng)建固定線程數(shù)量的線程池
        executorService = Executors.newFixedThreadPool(runSize);
        begin = new CountDownLatch(1);
        end = new CountDownLatch(runSize);
        //創(chuàng)建線程
        int startIndex = 0;
        int endIndex = 0;
        List<T> newList = null;
        for (int i = 0; i < runSize; i++) {
            //計(jì)算每個(gè)線程對(duì)應(yīng)的數(shù)據(jù)
            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);
            }
            //創(chuàng)建線程類處理數(shù)據(jù)
            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);
                    //具體執(zhí)行邏輯交給回調(diào)函數(shù)
                    try {
                        callBack.method(list);
                        /*if (random.nextInt(2) == 1) {
                            throw new RuntimeException("模擬異常拋出錯(cuò)誤回滾");
                        }*/
                        log.warn("多線程事務(wù)批量操作執(zhí)行成功,線程名:{},操作成功數(shù)量:{}",Thread.currentThread().getName(), list.size());
                    } catch (Exception e) {
                        // 接收異常,處理異常
                        isError.set(true);
                        //e.printStackTrace();
                        log.error("多線程事務(wù)批量操作拋錯(cuò),線程名:{},操作失敗數(shù)量:{},報(bào)錯(cuò)信息:{},{}",Thread.currentThread().getName(),list.size(),e.toString(), e);
                    }
                    //計(jì)數(shù)器減一
                    end.countDown();
                    try {
                        //等待所有線程任務(wù)完成荡短,監(jiān)控是否有異常,有則統(tǒng)一回滾
                        //log.warn("等待所有任務(wù)執(zhí)行完成,當(dāng)前時(shí)間:{},當(dāng)前end計(jì)數(shù):{}", LocalDateTime.now(), end.getCount());
                        end.await();
                        //log.warn("完成所有任務(wù),當(dāng)前時(shí)間:{},當(dāng)前end計(jì)數(shù):{}", LocalDateTime.now(), end.getCount());
                        if (isError.get()) {
                            // 事務(wù)回滾
                            transactionManager.rollback(status);
                        } else {
                            //事務(wù)提交
                            transactionManager.commit(status);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
            //執(zhí)行線程
            executorService.execute(myThread);
        }
        //計(jì)數(shù)器減一哆键,開始執(zhí)行任務(wù)  begin此時(shí)為0
        begin.countDown();//
        //等待任務(wù)全部執(zhí)行完畢掘托,變?yōu)?則任務(wù)全部完成
        end.await();
        //關(guān)閉線程池
        executorService.shutdown();
        //不拋錯(cuò)也是可以回滾的
        /*if (isError.get()) {
            // 主線程拋出自定義的異常
            throw new RuntimeException("主線程拋出模擬異常");
        }*/
    }

    //抽象線程類
    public abstract class MyThread<T> implements Runnable {
        //list:總數(shù)據(jù)分割后某線程負(fù)責(zé)執(zhí)行的數(shù)據(jù)
        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();
                //執(zhí)行程序
                method(list);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //計(jì)數(shù)器減一
                //end.countDown();
            }
        }

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

    //回調(diào)接口定義
    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) {
                //總數(shù)據(jù)分割后某線程負(fù)責(zé)執(zhí)行的數(shù)據(jù)
                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();
        }
    }
}


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市籍嘹,隨后出現(xiàn)的幾起案子闪盔,更是在濱河造成了極大的恐慌,老刑警劉巖辱士,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件泪掀,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颂碘,警方通過查閱死者的電腦和手機(jī)异赫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來头岔,“玉大人塔拳,你說我怎么就攤上這事∠靠ⅲ” “怎么了靠抑?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)适掰。 經(jīng)常有香客問我颂碧,道長(zhǎng)荠列,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任载城,我火速辦了婚禮肌似,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘诉瓦。我一直安慰自己锈嫩,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布垦搬。 她就那樣靜靜地躺著呼寸,像睡著了一般。 火紅的嫁衣襯著肌膚如雪猴贰。 梳的紋絲不亂的頭發(fā)上对雪,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音米绕,去河邊找鬼瑟捣。 笑死,一個(gè)胖子當(dāng)著我的面吹牛栅干,可吹牛的內(nèi)容都是我干的迈套。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼碱鳞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼桑李!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起窿给,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤贵白,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后崩泡,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體禁荒,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年角撞,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了呛伴。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡谒所,死狀恐怖热康,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情百炬,我是刑警寧澤褐隆,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布污它,位于F島的核電站剖踊,受9級(jí)特大地震影響庶弃,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜德澈,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一歇攻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梆造,春花似錦缴守、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至忽肛,卻和暖如春村砂,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屹逛。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工础废, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人罕模。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓评腺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親淑掌。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蒿讥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容