使用CountDownLatch时异常处理不当,导致接口无响应

背景

测试反馈,接口超时,没有数据返回。本地使用ApiFox调用接口,发现经过3-4分钟,接口迟迟没有数据返回。
image

image

解决

查看日志,发现有报错信息:

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();
    }

调用接口,有报错信息:
image

接口还是无响应,此种方式无效。

第二种

使用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);
        }
    }

image

跟第一种方式一样,此种方式无效。

第三种

使用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异常");
        }
    }

image

跟上面第一、二种方式一样,无效。

第四种

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();
        }
    }

image

此时服务正常运行,接口有返回值。
image

结论

使用CountDownLatch进行多线程编程时,一定要正确处理子线程的异常,同时要将countDownLatch.countDown()放到finally块中,保证countDown()方法一定会被执行

Reference

自定义线程池使用-countDownLatch卡死问题解决

posted @ 2024-07-19 17:58  Reecelin  阅读(10)  评论(0编辑  收藏  举报