第一步:创建现场池,注意不同业务场景线程池队列等参数不一样,根据实际业务场景来
package org.jeecg.modules.pUser.excutor;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;/*** @version 1.0* @Author zhaozhiqiang* @Date 2022/8/18 14:56* @Description //TODO 职业病团队批量导入线程池配置*/
@Slf4j
@Configuration
@EnableAsync// 启用异步任务 151-500
public class PeUserThreadPoolConfig implements AsyncConfigurer {public static void main(String[] args) {System.out.println(Runtime.getRuntime().availableProcessors());}/*** @version 1.0* @Author zhaozhiqiang* @Date 2022/8/19 10:59* @Description //TODO 团队批量预登记*/@Bean("teamImportExecutor")public ThreadPoolTaskExecutor teamImportTaskExecutor() {ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();// 设置核心线程数threadPoolTaskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors());// 设置最大线程数threadPoolTaskExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);// 设置队列容量,当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务threadPoolTaskExecutor.setQueueCapacity(5000);// 设置线程活跃时间(秒)非核心线程数 还回去的时间 如果空闲超过10秒就被回收threadPoolTaskExecutor.setKeepAliveSeconds(1);// 设置默认线程名称threadPoolTaskExecutor.setThreadNamePrefix("teamImport_");//用来设置线程池关闭的时候等待所有任务都完成(可以设置时间)threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);//设置上面的时间(秒)threadPoolTaskExecutor.setAwaitTerminationSeconds(1);//拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝/*** ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常* ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常* ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列* ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务*/threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());threadPoolTaskExecutor.initialize();return threadPoolTaskExecutor;}/*** @version 1.0* @Author zhaozhiqiang* @Date 2022/8/19 11:00* @Description //TODO 批量发布申请单*/@Bean("releaseBatchOrderExecutor")public ThreadPoolTaskExecutor releaseBatchOrderTaskExecutor() {ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();// 设置核心线程数threadPoolTaskExecutor.setCorePoolSize(2);// 设置最大线程数threadPoolTaskExecutor.setMaxPoolSize(4);// 设置队列容量,当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务threadPoolTaskExecutor.setQueueCapacity(30);// 设置线程活跃时间(秒)非核心线程数 还回去的时间 如果空闲超过10秒就被回收threadPoolTaskExecutor.setKeepAliveSeconds(1);// 设置默认线程名称threadPoolTaskExecutor.setThreadNamePrefix("releaseBatch_");//用来设置线程池关闭的时候等待所有任务都完成(可以设置时间)threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);//设置上面的时间(秒)threadPoolTaskExecutor.setAwaitTerminationSeconds(60);//拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝/*** ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常* ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常* ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列* ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务*/threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());threadPoolTaskExecutor.initialize();return threadPoolTaskExecutor;}
}
第二步:团队批量预登记使用如下(当前场景比较复杂,适用于大批量数据并且事务控制是按照部分回滚的;比如100条中有三条错误那么只回滚这三条的数据)
1 控制层代码/*** 批量登记** @return*/@ApiOperation(value = "批量预登记", notes = "批量预登记")@PostMapping(value = "/preBatchRegist")public Result> preBatchRegist(@RequestParam(value = "ids", required = true) String ids) {String token = UserTokenContext.getToken();peTeamImportService.preBatchRegist(ids,token);return Result.ok("批量登记成功,结果将陆续执行,请耐心等待所有数据全部执行完成");}
2实现层代码
@Override@Async("teamImportExecutor")public void preBatchRegist(String ids,String token) {UserTokenContext.setToken(token);if (StringUtils.isBlank(ids)) {log.info("批量预登记ids为空");return;}//1 查询数据库组装需要预登记的数据List list = Arrays.asList(ids.split(","));//将list集合按指定长度进行切分,返回新的List>集合List> idsSplitNumber = Lists.partition(list, BATCH_LIMIT_NUMBER);List peTeamImportPages = new ArrayList<>();idsSplitNumber.forEach(item -> {List peTeamImports = this.listByIds(item);List selfStrings = new ArrayList();peTeamImports.forEach(mainBean -> {selfStrings.add(mainBean.getId());});if (selfStrings.size() > 0) {//获取单位导入受检史List mainIds = peTeamImportSelfService.getMainIds(selfStrings);peTeamImports.forEach(mainItem -> {PeTeamImportPage vo = new PeTeamImportPage();BeanUtils.copyProperties(mainItem, vo);List childDto = new ArrayList<>();mainIds.forEach(childItem -> {if (StringUtils.equals(mainItem.getId(), childItem.getMainId())) {childDto.add(childItem);}});vo.setPeTeamImportSelfList(childDto);peTeamImportPages.add(vo);});}});if (peTeamImportPages.size() > 0) {//2 开始预登记for (int i = 0; i < peTeamImportPages.size(); i++) {try {peTeamImportSelfService.prebatchSend(peTeamImportPages.get(i));} catch (Exception e) {continue;}}}}
3 新事物控制实现类,当前控制新事务,单个有问题单个回滚
@Override@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)public void prebatchSend(PeTeamImportPage dto) {try {//预登记之前不能重复登记PeTeamImport byId = peTeamImportService.getById(dto.getId());if (null != byId && byId.getRegistFlag() == 1) {//已预登记过了不能重复登记log.info("编号:{},已经预登记了不能重复登记", dto.getId());throw new JeecgBootException("已经预登记了不能重复登记");}//2 往分组中添加人员信息,必须是为收费的批次,收费是不能添加人员信息的LambdaQueryWrapper departWrapper = new LambdaQueryWrapper<>();departWrapper.eq(PeDepart::getDelFlag, 0);departWrapper.eq(PeDepart::getId, dto.getDepartId());departWrapper.eq(PeDepart::getPayStatus, PeCommonConstant.UNPAID);PeDepart departOne = peDepartService.getOne(departWrapper);if (null == departOne) {log.info("改批次已经缴费或者退费:[{}]", dto.getDepartId());throw new JeecgBootException("改批次已经缴费或者退费");}//3.1分组名称和分组套餐必须先配置好,先判断库里分组是否存在、分组套餐是否已经被启用LambdaQueryWrapper groupWrapper = new LambdaQueryWrapper<>();groupWrapper.eq(PeDepartGroup::getDepartId, dto.getDepartId());groupWrapper.eq(PeDepartGroup::getGroupName, dto.getGroupName());groupWrapper.eq(PeDepartGroup::getDelFlag, 0);groupWrapper.eq(PeDepartGroup::getUseFlag, 1);groupWrapper.gt(PeDepartGroup::getTotalPrice, 0);//分组价格大于0PeDepartGroup groupOne = peDepartGroupService.getOne(groupWrapper);if (null == groupOne) {log.info(PeCodeConstant.DISABLE_IMPORT_GROUP_MSG);throw new JeecgBootException("改分组已经缴费或者退费");} else {//3.2 分组当前必须配置好套餐有项目可以检查LambdaQueryWrapper groupDetailsWrapper = new LambdaQueryWrapper<>();groupDetailsWrapper.eq(PeDepartGroupDetail::getGroupId, groupOne.getId());groupDetailsWrapper.eq(PeDepartGroupDetail::getDiseasesFlag, 1);groupDetailsWrapper.eq(PeDepartGroupDetail::getDelFlag, 0);List groupDetails = peDepartGroupDetailService.list(groupDetailsWrapper);if (null == groupDetails || groupDetails.size() == 0) {//当前分组没有配置套餐无法预登记log.info(PeCodeConstant.DISABLE_IMPORT_GROUP_ITEMS_MSG);throw new JeecgBootException(PeCodeConstant.DISABLE_IMPORT_GROUP_ITEMS_MSG);}}//4添加登记表、登记详情表、用户表等String peNumber = peRegistService.getPeNumber();//通过分组编号获取单位批次编号和单位批次名称PeUserInfo peUserInfo = null;try {peUserInfo = new PeUserInfo(0, dto.getName(), dto.getPhone(), null, dto.getDepartId(), dto.getSex(),dto.getMarriage(), dto.getIdno(), "" + dto.getAge(), departOne.getCompanyName());} catch (Exception e) {e.printStackTrace();log.info("用户相关信息填写不规范:[{}]", e.getMessage());throw new JeecgBootException("用户相关信息填写不规范");}//保存到体检用户表中userInfoService.save(peUserInfo);PeRegist build = new PeRegist();//工种类型需要转义成字典值if (StringUtils.isNotBlank(dto.getHazardType())) {String hazardTypeValue = formatHazardType(dto.getHazardType());if (StringUtils.isNotBlank(hazardTypeValue)) {build.setWorkeType(hazardTypeValue);}}build.setPeType(PeCommonConstant.REAM_TYPE);build.setPeNumber(peNumber);build.setPeUserId(peUserInfo.getId());build.setName(dto.getName());build.setOrderTime(departOne.getOrderTime());build.setPeDepartId(dto.getDepartId());build.setGroupId(groupOne.getId());build.setGroupName(dto.getGroupName());build.setPackDisFlag(PeCommonConstant.DIS_GROUP_TYPE);build.setCurRate(departOne.getDiscount());//单位折扣和个人的保持一直,为了个人和团队统一费用统计核算功能build.setSource(PeCommonConstant.WINDOW_REGISTER);//转义岗位状态if (StringUtils.isNotBlank(dto.getPosStatus())) {if (StringUtils.equals(dto.getPosStatus(), PeCommonConstant.POS_STATUS_PRE_STR)) {//上岗前build.setPosStatus(PeCommonConstant.POS_STATUS_PRE);} else if (StringUtils.equals(dto.getPosStatus(), PeCommonConstant.POS_STATUS_ZG_STR)) {//在岗期间build.setPosStatus(PeCommonConstant.POS_STATUS_ZG);} else if (StringUtils.equals(dto.getPosStatus(), PeCommonConstant.POS_STATUS_LG_STR)) {//离岗build.setPosStatus(PeCommonConstant.POS_STATUS_LG);}}build.setRemarks(PeCommonConstant.DISABLE_IMPORT_REMARKS);peRegistService.save(build);//成功之后更新导入标识已成功PeTeamImport entity = new PeTeamImport();entity.setId(dto.getId());entity.setRegistFlag(1);entity.setRegId(build.getId());peTeamImportService.updateById(entity);//查询单位批次分组信息List packgeInfoVos = peDepartGroupDetailService.queryCombInfoById(groupOne.getId());addRegistInfo(groupOne.getId(), build, packgeInfoVos);//添加当前人员项目对应的所有科室id多个逗号隔开String combDepartIds = peRegistMapper.getCombDepartIds(build.getId());build.setDepartIds(combDepartIds);peRegistService.updateById(build);//5添加职业病相关的表//插入职业病危害因素种类//判断数据库是否已经存在数据try {//转化空格String str = dto.getHazardName().replaceAll("\\s*", "");//将中文逗号转化英文逗号String finalStr = str.replaceAll(",", ",");String finalconsDetailStr = "[" + finalStr + "]";PeIndusConsDetailFactor detailFactor = new PeIndusConsDetailFactor(1, dto.getDepartId(), build.getId(), dto.getHazardCategory(), finalconsDetailStr);LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();wrapper.eq(PeIndusConsDetailFactor::getDelFlag, 0);wrapper.eq(PeIndusConsDetailFactor::getRegId, dto.getId());wrapper.eq(PeIndusConsDetailFactor::getIndusDepartId, dto.getDepartId());wrapper.eq(PeIndusConsDetailFactor::getDisType, 1);wrapper.eq(PeIndusConsDetailFactor::getFactorName, dto.getHazardCategory());PeIndusConsDetailFactor detailFactor1 = indusConsDetailFactorMapper.selectOne(wrapper);if (null == detailFactor1) {if (StringUtils.isNotBlank(dto.getHazardCategory()) && StringUtils.isNotBlank(dto.getHazardName())) {indusConsDetailFactorMapper.insert(detailFactor);}} else {detailFactor.setId(detailFactor1.getId());indusConsDetailFactorMapper.updateById(detailFactor);}} catch (Exception e) {e.printStackTrace();throw new JeecgBootException(e.getMessage());}//职业历史 indusConsDetailHistoryMapperLambdaQueryWrapper historyLambdaQueryWrapper = new LambdaQueryWrapper<>();historyLambdaQueryWrapper.eq(PeIndusConsDetailHistory::getRegId, build.getId());indusConsDetailHistoryMapper.delete(historyLambdaQueryWrapper);//受检本人历史先查看预分组信息中当前人的信息是否存在List peTeamImportSelfList = this.selectByMainId(dto.getId());if (null != peTeamImportSelfList && peTeamImportSelfList.size() > 0) {peTeamImportSelfList.stream().forEach(item -> {PeIndusConsDetailHistory history = new PeIndusConsDetailHistory(1, dto.getDepartId(), build.getId(), item.getCycletime(),item.getHarmFactor(), item.getProtectMeasures(), item.getUnit(), item.getWorkshop(), item.getWorktype());if (StringUtils.isNotBlank(item.getCycletime()) && StringUtils.isNotBlank(item.getHarmFactor()) && StringUtils.isNotBlank(item.getProtectMeasures())&& StringUtils.isNotBlank(item.getUnit()) && StringUtils.isNotBlank(item.getWorkshop()) && StringUtils.isNotBlank(item.getWorktype())) {indusConsDetailHistoryMapper.insert(history);}});}//慢性病历史 indusConsDetailCommonMapperLambdaQueryWrapper commonLambdaQueryWrapper = new LambdaQueryWrapper<>();commonLambdaQueryWrapper.eq(PeIndusConsDetailCommon::getRegId, build.getId());indusConsDetailCommonMapper.delete(commonLambdaQueryWrapper);if (StringUtils.isNotBlank(dto.getPastHistory()) || StringUtils.isNotBlank(dto.getSlowName()) || StringUtils.isNotBlank(dto.getOther())) {PeIndusConsDetailCommon consDetailCommon = new PeIndusConsDetailCommon(1, dto.getDepartId(), build.getId(), dto.getPastHistory(), dto.getSmokeStatus(), dto.getDrinkStatus(), dto.getSmokeAmount(), dto.getSmokeLength(), dto.getFirst(), dto.getCycle(), dto.getPeriod(), dto.getStop(), dto.getChildren(), dto.getFlow(),dto.getEarly(), dto.getDie(), dto.getAbnormal(), dto.getOther(), dto.getDrinkAmount(), dto.getDrinkLenth(), dto.getSlowName(), dto.getSlowTime(), dto.getSlowDepart(), dto.getHealthFlag());indusConsDetailCommonMapper.insert(consDetailCommon);}//症状 indusConsDetailDiseaseMapperLambdaQueryWrapper detailDiseaseLambdaQueryWrapper = new LambdaQueryWrapper<>();detailDiseaseLambdaQueryWrapper.eq(PeIndusConsDetailDisease::getIndusDepartId, dto.getDepartId());detailDiseaseLambdaQueryWrapper.eq(PeIndusConsDetailDisease::getRegId, build.getId());indusConsDetailDiseaseMapper.delete(detailDiseaseLambdaQueryWrapper);//转化空格String consDetailStr = dto.getSymptom().replaceAll("\\s*", "");//将中文逗号转化英文逗号String detailStr = consDetailStr.replaceAll(",", ",");//用集合包裹起来if (StringUtils.isNotBlank(dto.getSymptom())) {detailStr = "[" + detailStr + "]";PeIndusConsDetailDisease disease = new PeIndusConsDetailDisease(1, dto.getDepartId(), build.getId(), detailStr);indusConsDetailDiseaseMapper.insert(disease);}} catch (Exception e) {log.info("异常了:" + e.getMessage());throw e;}
第三步:批量发布申请单和批量预登记是一个场景,只不过当前的没有上面那个复杂,数据量不是很多
@Override@Async("releaseBatchOrderExecutor")public void releaseBatchOrder(String regIds,String token) {UserTokenContext.setToken(token);if (StringUtils.isNotBlank(regIds)) {List list = Arrays.asList(regIds.split(","));if (null != list && list.size() > 0) {for (int i = 0; i < list.size(); i++) {try {log.info("登记编号:{}", list.get(i));peLisContactService.batchSend(list.get(i));} catch (Exception e) {log.info("异常信息:{}", e.getMessage());continue;}}}}}@Override@Transactional(propagation = Propagation.REQUIRES_NEW,rollbackFor = Exception.class)public void batchSend(String regId) {try {//先判断是否lis那边已经推送过了String hosId = peChargeMapper.getHosId();//业务处理log.info("departFeeHandler regId:{} ", regId);//同步数据库和lis对接List peLisContacts = peLisContactMapper.queryListByRegId(regId, hosId);log.info("开始执行:{}条", peLisContacts.size());if (null != peLisContacts && peLisContacts.size() > 0) {log.info("开始插入条数:{}", peLisContacts.size());//批量插入this.saveBatch(peLisContacts);}} catch (Exception e) {log.info("异常了:" + e.getMessage());throw e;}}
package org.jeecg.common.util;import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;import java.util.concurrent.ThreadPoolExecutor;/*** @version 1.0* @Author zhaozhiqiang* @Date 2023/2/13 16:58* @Description //TODO 客服线程池*/
@Slf4j
@Configuration
@EnableAsync// 启用异步任务 151-500
public class CsThreadPoolConfig implements AsyncConfigurer {public static void main(String[] args) {System.out.println(Runtime.getRuntime().availableProcessors());}/*** @version 1.0* @Author zhaozhiqiang* @Date 2022/8/19 10:59* @Description //TODO频率高将队列容量设置30*/@Bean("feiqiuTask")public ThreadPoolTaskExecutor feiqiuTask() {ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();// 设置核心线程数threadPoolTaskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors());// 设置最大线程数threadPoolTaskExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);// 设置队列容量,当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务threadPoolTaskExecutor.setQueueCapacity(30);// 设置线程活跃时间(秒)非核心线程数 还回去的时间 如果空闲超过10秒就被回收threadPoolTaskExecutor.setKeepAliveSeconds(1);// 设置默认线程名称threadPoolTaskExecutor.setThreadNamePrefix("feiqiu_");//用来设置线程池关闭的时候等待所有任务都完成(可以设置时间)threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);//设置上面的时间(秒)threadPoolTaskExecutor.setAwaitTerminationSeconds(10);//拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝/*** ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常* ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常* ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列* ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务*/threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());threadPoolTaskExecutor.initialize();return threadPoolTaskExecutor;}/*** 发送短信* 频率高将队列容量设置100* @return*/@Bean("smsSendTask")public ThreadPoolTaskExecutor smsSendTask() {ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();// 设置核心线程数threadPoolTaskExecutor.setCorePoolSize(Runtime.getRuntime().availableProcessors());// 设置最大线程数threadPoolTaskExecutor.setMaxPoolSize(Runtime.getRuntime().availableProcessors() * 2);// 设置队列容量,当没有足够的线程去处理任务时,可以将任务放进队列中,以队列先进先出的特性来执行工作任务threadPoolTaskExecutor.setQueueCapacity(100);// 设置线程活跃时间(秒)非核心线程数 还回去的时间 如果空闲超过10秒就被回收threadPoolTaskExecutor.setKeepAliveSeconds(1);// 设置默认线程名称threadPoolTaskExecutor.setThreadNamePrefix("feiqiu_");//用来设置线程池关闭的时候等待所有任务都完成(可以设置时间)threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);//设置上面的时间(秒)threadPoolTaskExecutor.setAwaitTerminationSeconds(10);//拒绝策略:线程池都忙不过来的时候,可以适当选择拒绝/*** ThreadPoolExecutor.AbortPolicy();//默认,队列满了丢任务抛出异常* ThreadPoolExecutor.DiscardPolicy();//队列满了丢任务不异常* ThreadPoolExecutor.DiscardOldestPolicy();//将最早进入队列的任务删,之后再尝试加入队列* ThreadPoolExecutor.CallerRunsPolicy();//如果添加到线程池失败,那么主线程会自己去执行该任务*/threadPoolTaskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());threadPoolTaskExecutor.initialize();return threadPoolTaskExecutor;}
}
@Override@Async("feiqiuTask")public void sendFeiqiuSmg(String title, HttpServletRequest request, String msg, String userId, String token) {UserTokenContext.setToken(token);//发送feiqiu消息String userName = JwtUtil.getUserNameByToken(request);LoginUser user = sysUserService.getEncodeUserInfo(userName);if (null != user) {try {SysUser sendUserInfo = sysUserService.getById(userId);if (null != sendUserInfo && StringUtils.isNotBlank(sendUserInfo.getClientId())) {FeiQiuUtils.send(title, userName, sendUserInfo.getClientId(), msg);log.info("发送飞秋消息成功:{}", msg);}} catch (IOException e) {e.printStackTrace();log.error("发送飞秋消息失败:{}", msg);}}}
不推荐这样做的原因主要有三
1 要么全部成功要么全部失败,特别耗费连接资源数据量大的时候其它场景操作当前的表的时候等待,有可能操作死表
2 这这场景用的很少,或者几乎不用
3 这种场景后台进程特别耗时,失去了大批量异步的意义
把场景一稍微改造一下就是场景三,循环放到新事物里面就是全部滚回
当前的场景使用频率还是蛮高的,改造场景一将for循环的continue改成break就完了
下一篇:java并发 AQS