【多线程事务】- 思路与实现

一、场景

最近接到一个关于优化出库单批量确认的需求,在排查单线程、数据库等细节问题后,接口速度依旧很慢,于是决定使用多线程进行优化,同时使用到多线程事务,此处将该方案分享出来

二、思路

参考分布式事务的二阶段提交,由两个CountDownLatch来实现主线程、子线程的监控和全部提交

二阶段提交
1.当所有事务完成操作后,进入准备提交阶段,会向事务管理器发送事务准备成功信号。
2.事务管理器接受到所有成功信号后,通知所有事务进行提交 or 事务管理器接收到某事务发生异常,通知所有事务进行回滚操作。

三、测试代码

以下为测试Demo,结合具体业务场景使用。

1.依赖

<dependencies>
  <dependency>
      <groupId>p6spy</groupId>
      <artifactId>p6spy</artifactId>
      <version>3.9.1</version>
  </dependency>
  <dependency>
      <groupId>com.google.guava</groupId>
      <artifactId>guava</artifactId>
      <version>23.0</version>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>
  <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
  </dependency>
  <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.24</version>
      <optional>true</optional>
  </dependency>
</dependencies>

2.Entity


package com.example.demo.threadTransactional.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.io.Serializable;
import java.math.BigDecimal;

@Data
@Entity
@Table(name = "book")
@AllArgsConstructor
@NoArgsConstructor
public class Book implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @Column(name = "id", nullable = false)
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @Column(name = "name", nullable = false)
    private String name;

    @Column(name = "price")
    private BigDecimal price;

    public Book(String name, BigDecimal price) {
        this.name = name;
        this.price = price;
    }
}

3.RollBack

package com.example.demo.threadTransactional.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author qingzhou
 * @date 2022-12-09 16:50
 **/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class RollBack {
    private boolean isRollback;
}

3.Service

package com.example.demo.threadTransactional.service;

import com.example.demo.threadTransactional.entity.Book;
import com.example.demo.threadTransactional.entity.RollBack;
import com.example.demo.threadTransactional.repository.BookRepository;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.transaction.SystemException;
import java.util.List;
import java.util.concurrent.*;

/**
 * @author qingzhou
 * @date 2022-12-09 15:45
 **/
@Service
@Slf4j
public class BookService {

    @Resource
    private BookRepository bookRepository;

    @Resource
    @Lazy
    private BookService bookService;

    public void saveBook(List<Book> bookList) {

        // 根据业务场景,将大任务进行划分成小任务
        List<List<Book>> partition = Lists.partition(bookList, 3);

        // 子线程流程控制器
        CountDownLatch mainCountDownLatch = new CountDownLatch(1);

        // 回滚标记
        RollBack rollBack = new RollBack();

        // 主流程控制器
        CountDownLatch threadCountDownLatch = new CountDownLatch(partition.size());

        // 子线程预备执行结果
        BlockingDeque<Boolean> resultList = new LinkedBlockingDeque<>(partition.size());

        // 开启线程池
        int cpuNum = Runtime.getRuntime().availableProcessors() * 2;
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("multi-thread-%d").build();
        ExecutorService fixedThreadPool =
                new ThreadPoolExecutor(cpuNum, cpuNum, 0L, TimeUnit.MILLISECONDS,
                        new LinkedBlockingQueue<>(), threadFactory);

        // 创建线程任务并获取结果
        for (List<Book> list : partition) {
            fixedThreadPool.submit(new MyTask(list, mainCountDownLatch,
                    threadCountDownLatch, rollBack, resultList, bookService));
        }
        if (resultList.stream().anyMatch(result->!result)){
            rollBack.setRollback(true);
        }

        try {
            boolean await = threadCountDownLatch.await(10, TimeUnit.SECONDS);
            if (!await){
                // 指定时间子线程任务未执行完成,所有事务回滚
                log.error("子线程执行超时,所有事务回滚");
                rollBack.setRollback(true);
            }
        } catch (InterruptedException e) {
            // 发生异常,数据回滚
            rollBack.setRollback(true);
            throw new RuntimeException(e);
        }

        // 主线程监控事务完成
        mainCountDownLatch.countDown();

    }

    class MyTask implements Runnable{

        private final BookService bookService;

        private final List<Book> bookList;

        private final CountDownLatch mainCountDownLatch;

        private final CountDownLatch threadCountDownLatch;

        private final RollBack rollBack;

        BlockingDeque<Boolean> resultList;

        public MyTask(List<Book> bookList,
                      CountDownLatch mainCountDownLatch,
                      CountDownLatch threadCountDownLatch,
                      RollBack rollBack,
                      BlockingDeque<Boolean> resultList,
                      BookService bookService) {
            this.bookList = bookList;
            this.mainCountDownLatch = mainCountDownLatch;
            this.threadCountDownLatch = threadCountDownLatch;
            this.rollBack = rollBack;
            this.resultList = resultList;
            this.bookService = bookService;
        }

        public void run() {
            try {
                bookService.saveAll(bookList, mainCountDownLatch, threadCountDownLatch, rollBack, resultList);
            } catch (Exception e) {
                log.error(e.getMessage());
                throw new RuntimeException(e);
            }
        }
    }

    @Transactional(rollbackFor = Exception.class)
    public void saveAll(List<Book> bookList,
                            CountDownLatch mainCountDownLatch,
                            CountDownLatch threadCountDownLatch,
                            RollBack rollBack,
                            BlockingDeque<Boolean> threadDeque) throws Exception {
        try {
            for (Book book : bookList) {
                bookRepository.save(book);

                if ("book5".equals(book.getName())){
                    // 模拟某线程发生异常
                    int i = 1/0;
                }
            }
            threadDeque.add(true);
        } catch (Exception e) {
            log.error(String.format("线程%s发生异常,所有事务将回滚",Thread.currentThread().getName()));
            e.printStackTrace();
            rollBack.setRollback(true);
        }
        // 子线程准备提交事务
        threadCountDownLatch.countDown();

        // 等待主线程监听完所有事务处理情况
        mainCountDownLatch.await();

        // 子线程根据标记确定是否回滚
        if (rollBack.isRollback()){
            throw new SystemException(Thread.currentThread().getName() + " RollBack");
        }
    }
}

4.Controller

package com.example.demo.threadTransactional.controller;

import com.example.demo.threadTransactional.entity.Book;
import com.example.demo.threadTransactional.service.BookService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;

/**
 * @author qingzhou
 * @date 2022-12-09 17:16
 **/
@RestController
@AllArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {

    private final BookService bookService;

    @PostMapping("/test")
    public void test() {

        List<Book> books = new ArrayList<>();

        for (int i = 0; i < 10; i++){
            books.add(new Book("book"+i, BigDecimal.valueOf(i)));
        }
        bookService.saveBook(books);
    }

}

运行

http://localhost:8080/test
事务异常
查看数据库并未插入数据
异常-事务回滚
异常-事务回滚

正常执行:注释模拟异常,插入成功

正常执行
在这里插入图片描述

四、总结

在实际项目中,多线程进行任务拆分能提高效率,但需要考虑的因素也随之增多,如事务、线程安全性、实际提升效率等,因此应切合业务场景,优先考虑单线程优化,再考虑多线程优化。

同时,以上解决方案存在弊端,知道的小伙伴可以发在评论,大家一起学习进步~

文章如出现逻辑问题,欢迎批评指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值