一、场景
最近接到一个关于优化出库单批量确认的需求,在排查单线程、数据库等细节问题后,接口速度依旧很慢,于是决定使用多线程进行优化,同时使用到多线程事务,此处将该方案分享出来
二、思路
参考分布式事务的二阶段提交,由两个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
事务异常
查看数据库并未插入数据
正常执行:注释模拟异常,插入成功
四、总结
在实际项目中,多线程进行任务拆分能提高效率,但需要考虑的因素也随之增多,如事务、线程安全性、实际提升效率等,因此应切合业务场景,优先考虑单线程优化,再考虑多线程优化。
同时,以上解决方案存在弊端,知道的小伙伴可以发在评论,大家一起学习进步~
文章如出现逻辑问题,欢迎批评指正