2021-07-17

使用多线程批量插入数据到数据库

本文采用线程池的形式批量插入数据。
1.创建线程池以及核心参数简介:

/**
 * @Author yfx
 * @Date 2021/7/15
 * @Description
 */
public class ThreadPoolUtils {

    // 核心线程数
    private static final int CORE_POOL_SIZE = 4;
    // 最大线程数
    private static final int MAX_NUM_POOL_SIZE = 6;
    // 时间单位
    private static final TimeUnit UNIT = TimeUnit.SECONDS;
    // 线程存活时间 此处定义为5分钟
    private static final long KEEP_ALIVE_TIME = 60*5;
    // 阻塞队列,用于存储待执行的线程,队列长度根据实际情况定义
    private static final BlockingQueue WORK_QUEUE = new ArrayBlockingQueue(600);
    // 通过核心参数,使用new ThreadPoolExecutor()的构造函数的方式创建线程池,不建议使用Executors来创建线程,Executors中的阻塞队列没有定义长度,可以一直向阻塞队列中添加任务,可能造成内存不足
    public static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_NUM_POOL_SIZE,
            KEEP_ALIVE_TIME,
            UNIT,WORK_QUEUE);
    // 执行线程,提交到线程池中,无返回值,不利于异常捕获
    public static void execute(Runnable runnable) {
        threadPoolExecutor.execute(runnable);
    }
   // 提交到线程池,有返回值,可以实现异常捕获
    public static Future submit(Callable callable) {
        return threadPoolExecutor.submit(callable);
    }
   // 设置线程池状态 
   // 线程池状态:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED
    public static void shutdown() {
        threadPoolExecutor.shutdown();
    }

2.介绍线程池控制变量ctl:
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
线程池控制变量ctl: AtomicInteger类型:32位二进制,通过CAS+volitale实现,部分源码:

 private volatile int value;
        /**
         * Creates a new AtomicInteger with the given initial value.
         *
         * @param initialValue the initial value
         */
        public AtomicInteger(int initialValue) {
            value = initialValue;
        }
    ctl在线程池中的意义:
				            1.线程池状态    前3位
				            2.线程池工作线程数  后29位 

以下是源码内容以及本人的注释理解:

// 原子类AtomicInteger,保证线程安全,保证线程安全需要:原子性,可见性,有序性
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// 总工作线程二进制位数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 总工作线程数
// 000 11111111111111111111111111111111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits

// 101 00000000000000000000000000000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 000 00000000000000000000000000000000
private static final int SHUTDOWN   =  0 << COUNT_BITS; 
// SHUTDOWN不接收新的线程,执行完阻塞队列中的线程
// 001 00000000000000000000000000000000
private static final int STOP       =  1 << COUNT_BITS; 
// 010 00000000000000000000000000000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 011 00000000000000000000000000000000
private static final int TERMINATED =  3 << COUNT_BITS;

// 线程池状态:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED
// Packing and unpacking ctl
// 与运算后值只有前3位可能为1
private static int runStateOf(int c)     { return c & ~CAPACITY; }
// 与运算后值只有后29位可能为1
private static int workerCountOf(int c)  { return c & CAPACITY; }
// 得到一个完整的线程池控制变量
private static int ctlOf(int rs, int wc) { return rs | wc; }

3.介绍多线程使用实例:
controller

/**
 * @Author yfx
 * @Date 2021/7/15
 * @Description
 */
@RestController
public class BatchInsertController {

    @Autowired
    private BatchInsertService batchInsertService;

    @GetMapping
    public String BatchInsert() throws InterruptedException {
       return   batchInsertService.batchInsertByThreadPool();
    }
}

service

@Service
@Slf4j
public class BatchInsertServiceImpl implements BatchInsertService {

    @Autowired
    private BatchInsertMapper batchInsertMapper;

    @Override
    public String batchInsertByThreadPool() throws InterruptedException {
        List<User> userList = new ArrayList<>(100*10000);
        User user = new User(0, "测试", "测试", "测试", "测试", "测试");
        log.info("初始化数据");
        for (int i = 0; i<100*10000; i++) {
            userList.add(user);
        }
        log.info("初始化数据完毕{}"+userList.size()+"条");
        List<List<User>> partitionList = Lists.partition(userList, 2000);
        log.info("数据分组完毕{}"+partitionList.size()+"条");
        CountDownLatch countDownLatch = new CountDownLatch(partitionList.size());
        log.info("开始执行任务");
        long begin = System.currentTimeMillis();
        partitionList.forEach(x->ThreadPoolUtils.execute(()->{
            batchInsertMapper.batchInsert(x);
            countDownLatch.countDown();
        }));
        countDownLatch.await();
        log.info("执行完毕,耗时:{}"+(System.currentTimeMillis()-begin)+"ms");
        return "多线程插入数据成功";
    }
}

mapper
可能多个表都需要执行批量插入,不用反复在mapper接口中定义方法,所以创建了一个BaseMapper接口,其他要使用批量插入的接口继承该接口

/**
 * @Author yfx
 * @Date 2021/7/15
 * @Description
 */
public interface BaseMapper {
    void batchInsert(@Param("data") List<User> data);
}

/**
 * @Author yfx
 * @Date 2021/7/15
 * @Description
 */
@Repository
public interface BatchInsertMapper extends BaseMapper {
}

mapper.xml

 <insert id="batchInsert">
            insert into user(
            uid,
            username,
            password,
            nickname,
            gender,
            photo
            )
            values
            <foreach collection="data" item="item" index="index" separator=",">
                (
                #{item.uid},
                #{item.username},
                #{item.password},
                #{item.nickname},
                #{item.gender},
                #{item.photo}
                )
            </foreach>
    </insert>

执行结果:向mysql中插入100万条数据使用了30秒钟
在这里插入图片描述
4.补充说明:
由于本文使用的是多线程插入,所以通常我们使用的@Transactional事务控制不起作用,笔者使用的场景是向空表中添加测试数据,并且一个线程只执行官一次批量操作,所以如果出现异常是将表格式化 truncate table 表名,如果读者想要实现多线程事务控制,可采用list记录下没个线程执行结果的状态,如果有线程执行失败,则回滚所有数据。
5.介绍下计数器

// 创建时设置最大的计数器值
 CountDownLatch countDownLatch = new CountDownLatch(partitionList.size());
 // 常规条件下,执行完一个线程执行一次countDown()方法,使得计数器值减1,
 countDownLatch.countDown();
 // 使当前调用此方法的线程阻塞,当countDownLatch计数器的值减为0时被唤醒,本文条件下:即当多线程批量插入执行完毕后再唤醒主线程
 countDownLatch.await();

详细实现原理读者有兴趣可参考该文章:
https://blog.csdn.net/u010517268/article/details/113405518

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值