使用CountDownLatch时异常处理不当,导致接口无响应
背景
测试反馈,接口超时,没有数据返回。本地使用ApiFox
调用接口,发现经过3-4分钟,接口迟迟没有数据返回。
解决
查看日志,发现有报错信息:
Exception in thread "data-query-executor-7" org.springframework.jdbc.UncategorizedSQLException:
### Error querying database. Cause: java.sql.SQLException: errCode = 2, detailMessage = Syntax error in line 11:
group by tax_type_second ) a...
^
Encountered: GROUP
Expected: COMMA
### The error may exist in file [C:\Users\yslin5\Desktop\works\安徽税务\data-screen-parent\data-screen-service\target\classes\mapper\doris\RawDataDao.xml]
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: SELECT item, sum(cc) as count from (select sub as item, t.c as cc from ( SELECT split_by_string(tax_type_second, '\n') as dpl, count(1) as c from dwd_raw_data drdl WHERE (service_start_time >= ? AND service_start_time <= ? AND demand_source_channel = ? AND `show` = ? AND demand_channel IS NOT NULL AND demand_channel <> ?) GROUP BY demand_channel ORDER BY count DESC group by tax_type_second ) as t lateral view explode(t.dpl) tbl1 as sub) a group by item order by count desc;
### Cause: java.sql.SQLException: errCode = 2, detailMessage = Syntax error in line 11:
group by tax_type_second ) a...
^
Encountered: GROUP
Expected: COMMA
; uncategorized SQLException; SQL state [HY000]; error code [1105]; errCode = 2, detailMessage = Syntax error in line 11:
group by tax_type_second ) a...
^
Encountered: GROUP
Expected: COMMA
; nested exception is java.sql.SQLException: errCode = 2, detailMessage = Syntax error in line 11:
group by tax_type_second ) a...
^
Encountered: GROUP
Expected: COMMA
at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:93)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:439)
at com.sun.proxy.$Proxy114.selectList(Unknown Source)
at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:224)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.executeForMany(MybatisMapperMethod.java:164)
at com.baomidou.mybatisplus.core.override.MybatisMapperMethod.execute(MybatisMapperMethod.java:77)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy$PlainMethodInvoker.invoke(MybatisMapperProxy.java:152)
at com.baomidou.mybatisplus.core.override.MybatisMapperProxy.invoke(MybatisMapperProxy.java:89)
at com.sun.proxy.$Proxy120.getTaxCategoryStaticsByCriteria(Unknown Source)
at com.iflytek.screen.service.impl.DorisServiceImpl.getDataGroupByTaxCategories(DorisServiceImpl.java:140)
at com.iflytek.screen.service.impl.DorisServiceImpl$$FastClassBySpringCGLIB$$8a4a90c2.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.iflytek.screen.service.impl.DorisServiceImpl$$EnhancerBySpringCGLIB$$8cb0a84a.getDataGroupByTaxCategories(<generated>)
at com.iflytek.screen.service.impl.OverviewServiceImpl.getDataGroupByTaxCategories(OverviewServiceImpl.java:556)
at com.iflytek.screen.service.impl.OverviewServiceImpl$$FastClassBySpringCGLIB$$b8c74abe.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:793)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:763)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:708)
at com.iflytek.screen.service.impl.OverviewServiceImpl$$EnhancerBySpringCGLIB$$b87cf3be.getDataGroupByTaxCategories(<generated>)
at com.iflytek.screen.service.impl.TestServiceImpl.getDataGroupByTaxCategories(TestServiceImpl.java:83)
at com.iflytek.screen.service.impl.TestServiceImpl.lambda$getData$3(TestServiceImpl.java:52)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
原来是MySQL
进行分组的字段用错了,进行修改即可。
深入探究
但是,为什么报错了但是接口却迟迟不返回呢?这不太符合常理啊。于是便进行搜索。在这篇文章中,自定义线程池使用-countDownLatch卡死问题解决提到了,多线程抛出的异常无法被正常捕获,这样countDownLatch.countDown()也会一直阻塞,与我所遇到的问题正好相符。下面是相关业务的伪代码:
package com.iflytek.screen.service.impl;
import com.google.common.collect.Lists;
import com.iflytek.screen.service.OverviewService;
import com.iflytek.screen.service.TestService;
import com.iflytek.screen.vo.ScreenBaseRequest;
import com.iflytek.screen.vo.StaticsBaseVO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Classname TestServiceImpl
* @Description TODO
* @Date 2024/7/19 16:22
* @Created by Reece
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TestServiceImpl implements TestService {
private final OverviewService overviewService;
private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0);
private static final ExecutorService EXECUTOR_SERVICE = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1000), r -> new Thread(r, "data-query-executor-" + ATOMIC_INTEGER.incrementAndGet()), new ThreadPoolExecutor.CallerRunsPolicy());
@Override
public List<StaticsBaseVO> getData(ScreenBaseRequest request) {
List<StaticsBaseVO> result = Lists.newArrayList();
try {
CountDownLatch countDownLatch = new CountDownLatch(3);
EXECUTOR_SERVICE.execute(() -> getDataGroupByArea(request, result, countDownLatch));
EXECUTOR_SERVICE.execute(() -> getDataGroupByBusinessItem(request, result, countDownLatch));
EXECUTOR_SERVICE.execute(() -> getDataGroupByTaxCategories(request, result, countDownLatch));
// 等待所有子线程执行结束
countDownLatch.await();
log.info("全部数据已获取");
return result;
} catch (InterruptedException e) {
log.error("查询异常", e);
return result;
}
}
private void getDataGroupByArea(ScreenBaseRequest request, List<StaticsBaseVO> result, CountDownLatch countDownLatch) {
result.addAll(overviewService.getDataGroupByArea(request));
countDownLatch.countDown();
}
private void getDataGroupByBusinessItem(ScreenBaseRequest request, List<StaticsBaseVO> result,
CountDownLatch countDownLatch) {
try {
result.addAll(overviewService.getDataGroupByBusinessItem(request));
countDownLatch.countDown();
} catch (Exception e) {
log.error("获取getDataGroupByBusinessItem异常", e);
}
}
private void getDataGroupByTaxCategories(ScreenBaseRequest request, List<StaticsBaseVO> result,
CountDownLatch countDownLatch) {
result.addAll(overviewService.getDataGroupByTaxCategories(request));
countDownLatch.countDown();
}
}
可以看到,所有的countDownLatch.countDown()
方法都是在try-catch
中,且在主线程中调用的是countDownLatch.await()
方法,没有选择调用可以设置超时时间的哪个方法,即await(long timeout, TimeUnit unit)
,这样就会导致一旦子线程出现报错,那么子线程就会被阻塞住,又由于主线程调用的是countDownLatch.await()
方法,没有传入超时时间,则整个主线程也会被阻塞,进而导致服务被卡死。
测试如何在子线程中正常处理异常
修改代码,测试如何在子线程中正常处理异常,保证代码正常执行。
第一种情况
不加try-catch
块:
private void getDataGroupByBusinessItem(ScreenBaseRequest request, List<StaticsBaseVO> result, CountDownLatch countDownLatch) {
int i = 10 / 0;
result.addAll(overviewService.getDataGroupByBusinessItem(request));
countDownLatch.countDown();
}
调用接口,有报错信息:
接口还是无响应,此种方式无效。
第二种
使用try-catch
包住countDownLatch.countDown()
:
private void getDataGroupByBusinessItem(ScreenBaseRequest request, List<StaticsBaseVO> result,
CountDownLatch countDownLatch) {
try {
int i = 10 / 0;
result.addAll(overviewService.getDataGroupByBusinessItem(request));
countDownLatch.countDown();
} catch (Exception e) {
log.error("获取getDataGroupByBusinessItem异常", e);
}
}
跟第一种方式一样,此种方式无效。
第三种
使用try-catch
包住countDownLatch.countDown()
,同时抛出异常:
private void getDataGroupByBusinessItem(ScreenBaseRequest request, List<StaticsBaseVO> result,
CountDownLatch countDownLatch) {
try {
int i = 10 / 0;
result.addAll(overviewService.getDataGroupByBusinessItem(request));
countDownLatch.countDown();
} catch (Exception e) {
throw new RuntimeException("获取getDataGroupByBusinessItem异常");
}
}
跟上面第一、二种方式一样,无效。
第四种
将countDownLatch.countDown()放到finally块中:
private void getDataGroupByBusinessItem(ScreenBaseRequest request, List<StaticsBaseVO> result,
CountDownLatch countDownLatch) {
try {
int i = 10 / 0;
result.addAll(overviewService.getDataGroupByBusinessItem(request));
} catch (Exception e) {
throw new RuntimeException("获取getDataGroupByBusinessItem异常",e);
}finally {
// 放到finally 保证 countDown() 一定执行
countDownLatch.countDown();
}
}
此时服务正常运行,接口有返回值。
结论
使用CountDownLatch
进行多线程编程时,一定要正确处理子线程的异常,同时要将countDownLatch.countDown()放到finally块中,保证countDown()方法一定会被执行。