java 多线程批量插入数据 带返回值

之前写过一篇文章是关于多线程如何操作数据库,且控制事务的全局回滚,今天继续上一次进行扩展,上一次主要是针对单个线程操作没有返回值,而有时候我们希望进行多个线程批量操作数据库的同时,能返回每次成功插入到数据库的主键,这个时候就需要callable接口上场了,但是如果是希望线程执行结果是有返回的,还有很多地方需要注意的!特别是多个子线程和主线程协同操作,下面看一下具体的业务场景描述

场景描述:

现在有1万条数据需要插入到数据库,考虑到性能,我们会开启多个线程并发插入,来提高我们的程序插入性能

具体要求:

1 多线程插入操作

2 一个线程操作失败,需要回滚之前所有已经成功执行的线程

3 每个线程插入完以后,要返回对应的成功插入的主键

4 在插入数据之前,需要先进行删除或者更新操作,

那么要实现上面的功能,可以先简单画一个逻辑分析图,比如下面所示

首先因为需要返回值,所以我们可能需要创建一个包装线程执行结果的对象

package com.abcft.thread.transaction.common;
//包装线程执行的结果
public class CalcResult {
    private boolean success;
    private int result;

    public boolean isSuccess() {
        return success;
    }

    public void setResult(int result) {
        this.result = result;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public int getResult() {
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        return super.equals(obj);
    }

}

封装回滚对象

package com.abcft.thread.transaction.common;

public class DataRollBack {
    public DataRollBack(boolean isRollBack) {
        this.isRollBack = isRollBack;
    }
    
    //事务是否回滚 true回滚 false不回滚
    public boolean isRollBack() {
        return isRollBack;
    }
 
    public void setRollBack(boolean rollBack) {
        isRollBack = rollBack;
    }
 
    private boolean isRollBack;
 
 
}

封装模拟事务管理操作


public class TransactionManager {
    /**
     * 模拟事务回滚
     */
    public void rollBack(){
        System.out.println("线程:"+Thread.currentThread().getName()+"执行事务回滚");
    }
 
    /**
     * 模拟事务提交
     */
    public void commit(){
        System.out.println("线程:"+Thread.currentThread().getName()+"开始提交数据");
 
    }
}

模拟数据插入操作

package com.abcft.thread.transaction.common;
//模拟插入数据库的操作
public class InsertService {

    private UpdateService updateService = new UpdateService();

    // TODO: 2019-12-17 假设这里是使用spring来管理数据源以及事务
    //@autowired
    // JdbcTemplate jdbcTemplate
    public boolean insert(String data){
        // jdbcTemplate.insert(data)

        updateService.update(data);
        System.out.println("开始保存数据到数据库中");
        if(Thread.currentThread().getName().contains("thread-4") || Thread.currentThread().getName().contains("Thread-2"))
            return false;
        return true;
    }
}

模拟数据更新操作

package com.abcft.thread.transaction.common;
//模拟更新操作
public class UpdateService {
    // TODO: 2019-12-17 假设这里是使用spring来管理数据源以及事务
    //@autowired
    // JdbcTemplate jdbcTemplate
    public boolean update(String sql){
        //jdbcTemplate.update(sql);
        return true;
    }
}

 

封装数据业务逻辑相关操作

public class DataService {
    // 插入数据
    private InsertService insertService = new InsertService();
    // 修改数据
    private UpdateService updateService = new UpdateService();

    public CalcResult execute(String data){
        boolean insert = insertService.insert(data);
        boolean update = updateService.update(data);
        CalcResult calcResult = new CalcResult();
        if (insert && update){
            calcResult.setSuccess(true);
        }
        else {
            calcResult.setSuccess(false);
        }
        return calcResult;
    }
}

 

封装单个线程执行数据插入的操作

package com.abcft.thread.transaction.future;

import com.abcft.thread.transaction.common.*;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;

@SuppressWarnings("all")
public class DataCallable implements Callable<CalcResult> {


    //业务方法
    private DataService dataService;
    //主线程
    private CountDownLatch mainCountDownLatch;

    //子线程
    private CountDownLatch childCountDownLatch;
    //保存各个子线程执行的结果
    private BlockingQueue<Boolean> resultList;
    //事务管理
    private TransactionManager transactionManager;
    //数据结果执行决定是否回滚
    private DataRollBack dataRollBack;

    private CalcResult calcResult;


    public DataCallable(DataService dataService, CountDownLatch mainCountDownLatch, CountDownLatch childCountDownLatch, BlockingQueue<Boolean> resultList,
                        TransactionManager transactionManager, DataRollBack dataRollBack, CalcResult calcResult) {
        this.dataService = dataService;
        this.mainCountDownLatch = mainCountDownLatch;
        this.childCountDownLatch = childCountDownLatch;
        this.resultList = resultList;
        this.transactionManager = transactionManager;
        this.dataRollBack = dataRollBack;
        this.calcResult = calcResult;
    }


    @Override
    public CalcResult call() throws Exception {
        childCountDownLatch.countDown();
        int result1 = calcResult.getResult();
        calcResult.setResult(result1*10);
        System.out.println(Thread.currentThread().getName()+"子线程开始执行任务");
        // TODO: 2019-12-17 这块需要注意了 有可能会出现 Lock wait timeout exceeded; try restarting transaction 
        // TODO: 2019-12-17 原因是因为这里出现对数据库的多中操作 比如 插入操作 修改操作 导致 出现了事务层面的锁,也就是我们说的数据库层面的死锁
        // TODO: 2019-12-17 如何避免这个问题 那么就需要保证 操作和修改操作 不去抢夺事务执行资源
        // TODO: 2019-12-17 比如 insertservice 使用的是原生jdbc ,updateservice 使用的是基于spring 托管的jdbctemplate 这样它们就不会出现资源上的抢夺
        // TODO: 2019-12-17 当然 方法不止上面一种,主要能保证不同的数据库事务操作 对应不同的事务操作资源即可
        CalcResult execute = dataService.execute("test");
        calcResult.setSuccess(execute.isSuccess());
        resultList.add(execute.isSuccess());
        //子线程执行

        //阻塞主线程 回滚事务是由主线程来执行的
        try {
            // TODO: 2019-12-17 这行代码会导致  如果没有执行到主线程模块的时候,当前这个子线程是永远获取不到结果的 所以一定要注意!!!! 
            mainCountDownLatch.await();
            //在这里遍历

        } catch (Exception e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"子线程执行剩下的任务");

        if (dataRollBack.isRollBack()){
            transactionManager.rollBack();
        }
        else {
            transactionManager.commit();;
        }
        return calcResult;
    }
}

模拟主线程应用

package com.abcft.thread.transaction.main;
 
import com.abcft.thread.transaction.common.*;
import com.abcft.thread.transaction.future.DataCallable;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author hpjia@abcft.com
 * @version 1.0
 * @date
 * @description
 */
@SuppressWarnings("all")
public class DataCallableMain {
    public static void main(String[] args)throws Exception {
        int CHILD_SUM =5;
        //控制主线程执行的开关
        CountDownLatch mainCountDownLathc = new CountDownLatch(1);
        //控制子线程执行的开关
        CountDownLatch childCountDownLathc = new CountDownLatch(CHILD_SUM);
        //封装操作是否需要回滚
        DataRollBack dataRollBack = new DataRollBack(false);
        //实例化业务操作类
        DataService dataService = new DataService();
        //定义事务管理器 todo 不同的数据库操作 共用这一个事务管理器资源 会出现 Lock wait timeout exceeded; try restarting transaction 这种异常
        TransactionManager transactionManager = new TransactionManager();
        //存储每个线程执行结果的是否需要回滚的标识
        BlockingQueue<Boolean> resultList = new LinkedBlockingDeque<>(10);
        //创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //存储每个线程异步执行的结果
        List<Future<CalcResult>> fs =  new ArrayList<>();
        Future<CalcResult> submit = null;
        for (int i=0;i<5;i++){
            CalcResult calcResult = new CalcResult();
            calcResult.setResult(i);
            DataCallable dataCallable = new DataCallable(dataService,mainCountDownLathc,childCountDownLathc,resultList,transactionManager,dataRollBack,calcResult);
            submit =  executorService.submit(dataCallable);
            // TODO: 2019-12-17 这里需要注意submit方法是提交单个任务,且提交完了,不会立马返回结果
            // TODO: 2019-12-17 而invokeAll 提交的是多个任务,且是等待所有执行结果返回的,所以可以根据不同的场景来使用不同的方法 
            fs.add(submit);
        }
        

        int sum=0;
        //阻塞子线程 子线程不执行完 下面的逻辑不会被执行
        try {
            childCountDownLathc.await();

            // TODO: 2019-12-17 如果在这个地方获取各个子线程的结果 ,会出现在执行 CalcResult calcResult = calcResultFuture.get();
            // TODO: 2019-12-17 这行代码一直阻塞住,原因是因为这个线程是需要有返回结果的,而主线 mainCountDownLatch.await();这行代码会 
            // TODO: 2019-12-17 阻塞住 所以永远获取不到结果 而后面的其它子线程也不会被执行了 出现了互相等待的'死锁' ,所以需要放在执行主线程的代码前面
//            for (Future<CalcResult> calcResultFuture:fs){
//                CalcResult calcResult = calcResultFuture.get();
//                int result = calcResult.getResult();
//                System.out.println("res:"+result);
//                sum+=result;
//            }

            System.out.println("主线程开始执行任务");
            //通过队列获取结果
            for (int i=0;i<CHILD_SUM;i++){
                boolean calcResult = resultList.take();
                if (!calcResult){
                    dataRollBack.setRollBack(true);
                }
            }
            // 执行主线程 此时子线程应该是已经执行完了
            mainCountDownLathc.countDown();

            // TODO: 2019-12-17 执行主线程之前 循环拿到各个子线程的结果 然后再去执行主线程(到了要执行主线程这块代码,说明子线程的代码肯定都是执行完毕了,除非抛了异常)
            //提交线程池执行
            for (Future<CalcResult> calcResultFuture:fs){
                CalcResult calcResult = calcResultFuture.get();
                int result = calcResult.getResult();
                System.out.println("res:"+result);
                sum+=result;
            }

            // TODO: 2019-12-17 主线程 执行全局操作 事务提交或者回滚 
            if (dataRollBack.isRollBack()){
                System.out.println("执行线程出现了异常,需要进行全局事务回滚");
            }
            else {
                System.out.println("多线程插入数据已经全部完毕且提交事务了");
            }
 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("result:"+sum);

        executorService.shutdown();
    }
}

总结:使用的过程和注意点 具体看上面代码上的具体注释

  • 0
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值