在日常搬砖过程中,有时考虑业务处理性能,会使用@Async异步处理耗时操作
@Configuration
@EnableAsync
public class ThreadPoolTaskConfig {
/**
* 自定义线程池
* ThreadPoolTaskExecutor 是对 ThreadPoolExecutor的封装,是spring提供的。
*/
@Bean("threadPoolTaskExecutor")
public ThreadPoolTaskExecutor threadPoolTaskExecutor(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//线程池创建的核心线程数,线程池维护线程的最少数量,即使没有任务需要执行,也会一直存活
executor.setCorePoolSize(16);
//如果设置allowCoreThreadTimeout=true(默认false)时,核心线程会超时关闭
//executor.setAllowCoreThreadTimeOut(true);
//阻塞队列 当核心线程数达到最大时,新任务会放在队列中排队等待执行
executor.setQueueCapacity(124);
//最大线程池数量,当线程数>=corePoolSize,且任务队列已满时。线程池会创建新线程来处理任务
//任务队列已满时, 且当线程数=maxPoolSize,,线程池会拒绝处理任务而抛出异常
executor.setMaxPoolSize(64);
//当线程空闲时间达到keepAliveTime时,线程会退出,直到线程数量=corePoolSize
//允许线程空闲时间30秒,当maxPoolSize的线程在空闲时间到达的时候销毁
//如果allowCoreThreadTimeout=true,则会直到线程数量=0
executor.setKeepAliveSeconds(30);
//spring 提供的 ThreadPoolTaskExecutor 线程池,是有setThreadNamePrefix() 方法的。
//jdk 提供的ThreadPoolExecutor 线程池需要通过设置threadfactory设置线程名
executor.setThreadNamePrefix("coolThread-");
// rejection-policy:拒绝策略:当线程数已经达到maxSize的时候,如何处理新任务
// CallerRunsPolicy():交由调用方线程运行,比如 main 线程;如果添加到线程池失败,那么主线程会自己去执行该任务,不会等待线程池中的线程去执行, (个人推荐)
// AbortPolicy():该策略是线程池的默认策略,如果线程池队列满了丢掉这个任务并且抛出RejectedExecutionException异常。
// DiscardPolicy():如果线程池队列满了,会直接丢掉这个任务并且不会有任何异常
// DiscardOldestPolicy():丢弃队列中最老的任务,队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//设置线程池关闭的时候等待所有任务都完成再继续销毁其他的Bean,这样这些异步任务的销毁就会先于Redis线程池的销毁
executor.setWaitForTasksToCompleteOnShutdown(true);
//设置线程池中任务的等待时间,如果超过这个时候还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是阻塞住。
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}
public interface SourcesService {
void jtaTest01(String mainThreadName);
void jtaTest02(String mainThreadName);
}
@Slf4j
@Service
public class SourcesServiceImpl implements SourcesService {
@Autowired
TestTxAMapper testTxAMapper;
@Autowired
TestTxBMapper testTxBMapper;
@Async("threadPoolTaskExecutor")
@Override
@Transactional(rollbackFor = Exception.class)
public void jtaTest01(String mainThreadName) {
// DB2 tb1
TestTxA testTxA = new TestTxA();
testTxA.setTestName("DB2 tb1 --- tb1");
testTxAMapper.insert(testTxA);
try{
// 其他业务
// .......
TimeUnit.SECONDS.sleep(3);
}catch (Exception e){
log.error("",e);
}
log.info("insert db2 tb1 ...");
throw new RuntimeException();
}
@Async("threadPoolTaskExecutor")
@Override
@Transactional(rollbackFor = Exception.class)
public void jtaTest02(String mainThreadName) {
// DB2 tb2
TestTxB testTxB = new TestTxB();
testTxB.setTestCode("DB2 tb2 --- tb2");
testTxBMapper.insert(testTxB);
log.info("insert db2 tb2 ...");
}
}
现有另一个业务接口内,会调用以上两个方法,但业务要求原子性。异步调用的话会是spring的声明式事务失效,所以思路是使用编程式事务。
这里再加上多数据源,使用JTA实现多源事务
;
@MainTransaction
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MainTransaction {
// 子线程数量
int value();
}
@SonTransaction
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface SonTransaction {
}
将注解添加到对应的方法上,@MainTransaction
打到主线程方法上,参数为需要调用的异步事务方法数量;@SonTransaction
打到异步事务方法上。
/**
* 主方法写数据到DB1中
* sourcesService.jtaTest01 异步写数据到 DB2的tb1中
* sourcesService.jtaTest02 异步写数据到 DB2的tb2中
* 改事务一共涉及2个库,三张表
**/
@Override
@MainTransaction(value = 2)
@Transactional(rollbackFor = Exception.class)
public void jtaTest() {
sourcesService.jtaTest01(Thread.currentThread().getName());
// DB1
TxJTATest txJTATest = new TxJTATest();
txJTATest.setName("DB1 --- db1");
txJTATestMapper.insert(txJTATest);
log.info("insert db1 ...");
sourcesService.jtaTest02(Thread.currentThread().getName());
}
// 这里使用JTA事务,如果是spring默认的是 PlatformTransactionManager
@Resource
JtaTransactionManager jtaTransactionManager;
@Slf4j
@Aspect
@Component
public class TransactionAop {
//用来存储各线程计数器数据(每次执行后会从map中删除)
private static final Map<String, Object> map = new HashMap<>();
// 这里使用JTA事务,如果是spring默认的是 PlatformTransactionManager
@Resource
JtaTransactionManager jtaTransactionManager;
@Around("@annotation(mainTransaction)")
public void mainIntercept(ProceedingJoinPoint joinPoint, MainTransaction mainTransaction) throws Throwable {
// 获取当前线程
Thread thread = Thread.currentThread();
String threadName = thread.getName();
log.info("mainTransaction at : {}",threadName);
//初始化计数器
CountDownLatch mainDownLatch = new CountDownLatch(1);
CountDownLatch sonDownLatch = new CountDownLatch(mainTransaction.value());
// 用来记录子线程的运行状态,只要有一个失败就变为true
AtomicBoolean rollBackFlag = new AtomicBoolean(false);
// 用来存每个子线程的异常,把每个线程的自定义异常向vector的首位置插入,其余异常向末位置插入,避免线程不安全,所以使用vector代替list
Vector<Throwable> exceptionVector = new Vector<>();
map.put(threadName + "_mainDownLatch", mainDownLatch);
map.put(threadName + "_sonDownLatch", sonDownLatch);
map.put(threadName + "_rollBackFlag", rollBackFlag);
map.put(threadName + "_exceptionVector", exceptionVector);
try {
//执行方法
joinPoint.proceed();
} catch (Throwable e) {
// 如果抛异常
exceptionVector.add(0, e);
rollBackFlag.set(true);//子线程回滚
mainDownLatch.countDown();//放行所有子线程
}
// 如果未抛出异常
if (!rollBackFlag.get()) {
try {
// sonDownLatch等待,直到所有子线程执行完插入操作,但此时还没有提交事务
sonDownLatch.await();
log.info("sonThread is down ...");
mainDownLatch.countDown();// 根据rollBackFlag状态放行子线程的await处,告知是回滚还是提交
} catch (Exception e) {
log.error("mainIntercept error",e);
rollBackFlag.set(true);
exceptionVector.add(0, e);
}
}
// 如果有异常
if (CollectionUtils.isNotEmpty(exceptionVector)) {
map.remove(threadName + "_mainDownLatch");
map.remove(threadName + "_sonDownLatch");
map.remove(threadName + "_rollBackFlag");
map.remove(threadName + "_exceptionVector");
throw exceptionVector.get(0);
}
}
@Around("@annotation(com.lcx.more.annotation.SonTransaction)")
public void sonIntercept(ProceedingJoinPoint joinPoint) throws Throwable {
Thread thread = Thread.currentThread();
String threadName = thread.getName();
log.info("SonTransaction at : {}",threadName);
// 主线程的线程名,需通过参数传入
Object[] args = joinPoint.getArgs();
String mainThread = (String) args[args.length - 1];
if(StringUtils.isBlank(mainThread)){
log.error("sonTransaction 未获取到 mainTransaction thread info!");
throw new RuntimeException();
}
CountDownLatch mainDownLatch = (CountDownLatch) map.get(mainThread + "_mainDownLatch");
CountDownLatch sonDownLatch = (CountDownLatch) map.get(mainThread + "_sonDownLatch");
AtomicBoolean rollBackFlag = (AtomicBoolean) map.get(mainThread + "_rollBackFlag");
Vector<Throwable> exceptionVector = (Vector<Throwable>) map.get(mainThread + "_exceptionVector");
if (mainDownLatch == null) {
//主事务未加注解时, 直接执行子事务
joinPoint.proceed();
sonDownLatch.countDown();
return;
}
//如果这时有一个子线程已经出错,那当前线程不需要执行
if (rollBackFlag.get()) {
sonDownLatch.countDown();
return;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 开启事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 设置事务隔离级别
TransactionStatus status = jtaTransactionManager.getTransaction(def);
try {
log.info("{} start",threadName);
joinPoint.proceed();//执行方法
log.info("{} countDown",threadName);
sonDownLatch.countDown();// 对sonDownLatch-1
mainDownLatch.await();// 如果mainDownLatch不是0,线程会在此阻塞,直到mainDownLatch变为0
// 如果能执行到这一步说明所有子线程都已经执行完毕判断如果atomicBoolean是true就回滚false就提交
if (rollBackFlag.get()) {
log.info("transaction rollback");
jtaTransactionManager.rollback(status);
} else {
log.info("transaction commit");
jtaTransactionManager.commit(status);
}
} catch (Throwable e) {
log.error("sonIntercept error : ",e.getMessage());
exceptionVector.add(0, e);
// 回滚
jtaTransactionManager.rollback(status);
// 并把状态设置为true
rollBackFlag.set(true);
mainDownLatch.countDown();
sonDownLatch.countDown();
}
}
}