diff --git a/threadthractional/woniu-web-master/.gitignore b/threadthractional/woniu-web-master/.gitignore new file mode 100644 index 0000000..682d4ed --- /dev/null +++ b/threadthractional/woniu-web-master/.gitignore @@ -0,0 +1,27 @@ +# Created by .ignore support plugin (hsz.mobi) +### Java template +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/.idea/ +/target/ diff --git a/threadthractional/woniu-web-master/pom.xml b/threadthractional/woniu-web-master/pom.xml new file mode 100644 index 0000000..b3c49fb --- /dev/null +++ b/threadthractional/woniu-web-master/pom.xml @@ -0,0 +1,105 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.3.9.RELEASE + + + + com.ayi + woniu-web + 0.0.1-SNAPSHOT + woniu-web + create by woniu + + jar + + + 1.8 + + + + + org.springframework.boot + spring-boot-starter-aop + + + cn.hutool + hutool-all + 5.3.3 + + + org.springframework.boot + spring-boot-starter + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + + + org.springframework.boot + spring-boot-starter-cache + + + + + + com.baomidou + mybatis-plus-boot-starter + 3.4.1 + + + + + mysql + mysql-connector-java + 8.0.29 + + + + com.alipay.sofa + runtime-sofa-boot-starter + 3.18.0 + + + + org.springframework.boot + spring-boot-starter-validation + + + + + com.alibaba + fastjson + 1.2.76 + + + com.alibaba + easyexcel + 3.0.5 + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/WoNiuApplication.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/WoNiuApplication.java new file mode 100644 index 0000000..dfa2912 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/WoNiuApplication.java @@ -0,0 +1,20 @@ +package com.woniu; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.transaction.annotation.EnableTransactionManagement; + +@SpringBootApplication +@EnableTransactionManagement +@EnableAsync +public class WoNiuApplication { + + public static void main(String[] args) { + SpringApplication.run(WoNiuApplication.class, args); + } + + + +} diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/MainTransaction.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/MainTransaction.java new file mode 100644 index 0000000..138828c --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/MainTransaction.java @@ -0,0 +1,17 @@ +package com.woniu.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @className: MainTransaction + * @author: woniu + * @date: 2023/6/27 + **/ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface MainTransaction { + int value();//子线程数量 +} diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/SonTransaction.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/SonTransaction.java new file mode 100644 index 0000000..cee8070 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/anno/SonTransaction.java @@ -0,0 +1,18 @@ +package com.woniu.anno; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * 多线程事务注解: 子事务 + * + * @author zlj + * @since 2022/11/3 + */ +@Target({ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface SonTransaction { + String value() default ""; +} \ No newline at end of file diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/aop/TransactionAop.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/aop/TransactionAop.java new file mode 100644 index 0000000..61dd4cd --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/aop/TransactionAop.java @@ -0,0 +1,125 @@ +package com.woniu.aop; + +import com.woniu.anno.MainTransaction; +import org.apache.commons.collections4.CollectionUtils; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.DefaultTransactionDefinition; + +import javax.annotation.Resource; +import java.util.HashMap; +import java.util.Map; +import java.util.Vector; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * SpringBoot 中的多线程事务处理太繁琐?一个自定义注解直接搞定! + */ +@Aspect +@Component +public class TransactionAop { + + //用来存储各线程计数器数据(每次执行后会从map中删除) + private static final Map map = new HashMap<>(); + + @Resource + private PlatformTransactionManager transactionManager; + + @Around("@annotation(mainTransaction)") + public void mainIntercept(ProceedingJoinPoint joinPoint, MainTransaction mainTransaction) throws Throwable { + //当前线程名称 + Thread thread = Thread.currentThread(); + String threadName = thread.getName(); + //初始化计数器 + CountDownLatch mainDownLatch = new CountDownLatch(1); + CountDownLatch sonDownLatch = new CountDownLatch(mainTransaction.value());//@MainTransaction注解中的参数, 为子线程的数量 + // 用来记录子线程的运行状态,只要有一个失败就变为true + AtomicBoolean rollBackFlag = new AtomicBoolean(false); + // 用来存每个子线程的异常,把每个线程的自定义异常向vector的首位置插入,其余异常向末位置插入,避免线程不安全,所以使用vector代替list + Vector 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(); + mainDownLatch.countDown();// 根据rollBackFlag状态放行子线程的await处,告知是回滚还是提交 + } catch (Exception 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.woniu.anno.SonTransaction)") + public void sonIntercept(ProceedingJoinPoint joinPoint) throws Throwable { + Object[] args = joinPoint.getArgs(); + Thread thread = (Thread) args[args.length - 1]; + String threadName = thread.getName(); + CountDownLatch mainDownLatch = (CountDownLatch) map.get(threadName + "mainDownLatch"); + if (mainDownLatch == null) { + //主事务未加注解时, 直接执行子事务 + joinPoint.proceed(); + return; + } + CountDownLatch sonDownLatch = (CountDownLatch) map.get(threadName + "sonDownLatch"); + AtomicBoolean rollBackFlag = (AtomicBoolean) map.get(threadName + "rollBackFlag"); + Vector exceptionVector = (Vector) map.get(threadName + "exceptionVector"); + + //如果这时有一个子线程已经出错,那当前线程不需要执行 + if (rollBackFlag.get()) { + sonDownLatch.countDown(); + return; + } + + DefaultTransactionDefinition def = new DefaultTransactionDefinition();// 开启事务 + def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);// 设置事务隔离级别 + TransactionStatus status = transactionManager.getTransaction(def); + + try { + joinPoint.proceed();//执行方法 + + sonDownLatch.countDown();// 对sonDownLatch-1 + mainDownLatch.await();// 如果mainDownLatch不是0,线程会在此阻塞,直到mainDownLatch变为0 + // 如果能执行到这一步说明所有子线程都已经执行完毕判断如果atomicBoolean是true就回滚false就提交 + if (rollBackFlag.get()) { + transactionManager.rollback(status); + } else { + transactionManager.commit(status); + } + } catch (Throwable e) { + exceptionVector.add(0, e); + // 回滚 + transactionManager.rollback(status); + // 并把状态设置为true + rollBackFlag.set(true); + mainDownLatch.countDown(); + sonDownLatch.countDown(); + } + } +} \ No newline at end of file diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/config/MybatisPlusConfig.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/config/MybatisPlusConfig.java new file mode 100644 index 0000000..da6c0a0 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/config/MybatisPlusConfig.java @@ -0,0 +1,30 @@ +package com.woniu.config; + +import com.baomidou.mybatisplus.annotation.DbType; +import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; +import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; +import org.mybatis.spring.annotation.MapperScan; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.transaction.annotation.EnableTransactionManagement; + + +@Configuration +@MapperScan("com.woniu") +@EnableTransactionManagement +public class MybatisPlusConfig { + + /** + * 新的分页插件,一缓和二缓遵循mybatis的规则, + * 需要设置 MybatisConfiguration#useDeprecatedExecutor = false + * 避免缓存出现问题(该属性会在旧插件移除后一同移除) + */ + @Bean + public MybatisPlusInterceptor mybatisPlusInterceptor() { + MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); + interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); + return interceptor; + } + + +} diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/config/ThreadPoolTaskConfig.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/config/ThreadPoolTaskConfig.java new file mode 100644 index 0000000..d58d712 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/config/ThreadPoolTaskConfig.java @@ -0,0 +1,57 @@ +package com.woniu.config; + + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; + +import java.util.concurrent.ThreadPoolExecutor; + +@Configuration +@EnableAsync +public class ThreadPoolTaskConfig { + + @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 线程池是没有 setThreadNamePrefix() 方法的 + executor.setThreadNamePrefix("自定义线程池-"); + + // 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; + } +} \ No newline at end of file diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/controller/AddressController.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/controller/AddressController.java new file mode 100644 index 0000000..cec8316 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/controller/AddressController.java @@ -0,0 +1,32 @@ +package com.woniu.controller; + +import com.woniu.service.MainService; +import com.woniu.vo.ApiResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + + +/** + * SpringBoot中的多线程事务处理太繁琐?一个自定义注解直接搞定! + */ +@RequestMapping("/api") +@RestController +@Slf4j +public class AddressController { + + + @Autowired + MainService mainService; + + + @PostMapping("/get2") + public ApiResponse get() { + mainService.test1(); + return ApiResponse.ok(null); + } + + + + +} diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/service/MainService.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/service/MainService.java new file mode 100644 index 0000000..ddfa720 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/service/MainService.java @@ -0,0 +1,27 @@ +package com.woniu.service; + + +import com.woniu.anno.MainTransaction; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import javax.annotation.Resource; + + +@Service +public class MainService { + + @Resource + private SonService sonService; + + @MainTransaction(3) //调用的方法中sonMethod1/sonMethod2/sonMethod3使用@Async开启了线程, 所以参数为: 3 + @Transactional(rollbackFor = Exception.class) + public void test1() { + sonService.sonMethod1("蜗牛", Thread.currentThread()); + sonService.sonMethod2("乾隆", "山治", Thread.currentThread()); + sonService.sonMethod3("小宝", Thread.currentThread()); + sonService.sonMethod4("罗宾"); + } + + +} \ No newline at end of file diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/service/SonService.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/service/SonService.java new file mode 100644 index 0000000..55bb3c6 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/service/SonService.java @@ -0,0 +1,49 @@ +package com.woniu.service; + +import com.woniu.anno.SonTransaction; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +/** + * @author woniu + * @since 2023/06/28 + */ +@Service +public class SonService { + + /** + * 参数说明: 以下4个方法参数和此相同 + * + * @param args 业务中需要传递的参数 + * @param thread 调用者的线程, 用于aop获取参数, 不建议以方法重写的方式简略此参数, + * 在调用者方法中可以以此参数为标识计算子线程的个数作为注解参数,避免线程参数计算错误导致锁表 + * 传参时参数固定为: Thread.currentThread() + */ + @Transactional(rollbackFor = Exception.class) + @Async("threadPoolTaskExecutor") + @SonTransaction + public void sonMethod1(String args, Thread thread) { + System.out.println(args + "开启了线程"); + } + + @Transactional(rollbackFor = Exception.class) + @Async("threadPoolTaskExecutor") + @SonTransaction + public void sonMethod2(String args1, String args2, Thread thread) { + System.out.println(args1 + "和" + args2 + "开启了线程"); + } + + @Transactional(rollbackFor = Exception.class) + @Async("threadPoolTaskExecutor") + @SonTransaction + public void sonMethod3(String args, Thread thread) { + System.out.println(args + "开启了线程"); + } + + //sonMethod4方法没有使用线程池 + @Transactional(rollbackFor = Exception.class) + public void sonMethod4(String args) { + System.out.println(args + "没有开启线程"); + } +} \ No newline at end of file diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/ApiResponse.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/ApiResponse.java new file mode 100644 index 0000000..fd91231 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/ApiResponse.java @@ -0,0 +1,26 @@ +package com.woniu.vo; + +import lombok.Data; + +@Data +public class ApiResponse { + + private T data; + private String message; + private String code; + + public ApiResponse(T data, String message, String code) { + this.data = data; + this.message = message; + this.code = code; + } + + + public static ApiResponse fail(T data, MessageEnum messageEnum) { + return new ApiResponse(data, messageEnum.getMessage(), messageEnum.getCode()); + } + + public static ApiResponse ok(T data) { + return new ApiResponse(data, "响应正常", "200"); + } +} diff --git a/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/MessageEnum.java b/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/MessageEnum.java new file mode 100644 index 0000000..2baa785 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/java/com/woniu/vo/MessageEnum.java @@ -0,0 +1,28 @@ +package com.woniu.vo; + +/** + * @className: MessageEnum + * @author: woniu + * @date: 2023/6/25 + **/ + + +public enum MessageEnum { + ParamException("400","参数错误!"); + + private String code; + private String message; + + MessageEnum(String code, String message) { + this.code = code; + this.message = message; + } + + public String getCode() { + return code; + } + + public String getMessage() { + return message; + } +} diff --git a/threadthractional/woniu-web-master/src/main/resources/application.properties b/threadthractional/woniu-web-master/src/main/resources/application.properties new file mode 100644 index 0000000..0a75ef4 --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/resources/application.properties @@ -0,0 +1,12 @@ +spring.datasource.username=root +spring.datasource.password=123456 +spring.datasource.url=jdbc:mysql://localhost:3306/test?serverTimezone=UTC +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +mybatis.mapper-locations=classpath:/mapper/*.xml + +spring.cache.type=simple +spring.application.name=yyyy +#spring.jackson.date-format=yyyy-MM-dd HH:mm:ss +#spring.jackson.time-zone=GMT+8 + diff --git a/threadthractional/woniu-web-master/src/main/resources/logback-spring.xml b/threadthractional/woniu-web-master/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..9b029bd --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/resources/logback-spring.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + ${CONSOLE_LOG_PATTERN} + + UTF-8 + + + + + + + + + + + diff --git a/threadthractional/woniu-web-master/src/main/resources/mybatis-config.xml b/threadthractional/woniu-web-master/src/main/resources/mybatis-config.xml new file mode 100644 index 0000000..b099e5d --- /dev/null +++ b/threadthractional/woniu-web-master/src/main/resources/mybatis-config.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/threadthractional/woniu-web-master/woniu-web.iml b/threadthractional/woniu-web-master/woniu-web.iml new file mode 100644 index 0000000..033afd6 --- /dev/null +++ b/threadthractional/woniu-web-master/woniu-web.iml @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file