Future多线程导致系统阻塞

多线程使用时线程阻塞

1. 背景

     因为本人工作是对大量数据进行计算,用户的每个操作都可能引发后台的大量运算。
所以我在后端用了个线程池工具类。
    该工具在系统启动是默认创建了一个线程池,该线程池有50个核心线程,4096个阻塞队列,系统中使用多线程的地方都直接调用ThreadPoolUtils.exec(task)或者ThreadPoolUtils.execCallable(Callable<?> task),如下:

public class ThreadPoolUtils {

    /**
     * 核心线程池大小
     */
    public static final int CORE_POOL_SIZE = 50;
    /**
     * 最大线程池大小
     */
    public static final int MAX_POOL_SIZE = 100;
    /**
     * 阻塞任务队列大小
     */
    public static final int QUEUE_CAPACITY = 4096;
    /**
     * 空闲线程存活时间
     */
    public static final Long KEEP_ALIVE_TIME = 60L;
    private final static Logger logger = LoggerFactory.getLogger(ThreadPoolUtils.class);
    private static final ThreadPoolExecutor EXEC = new ThreadPoolExecutor(
            CORE_POOL_SIZE,
            MAX_POOL_SIZE,
            KEEP_ALIVE_TIME,
            TimeUnit.SECONDS,
            new ArrayBlockingQueue<>(QUEUE_CAPACITY),
            new ThreadPoolExecutor.CallerRunsPolicy());


    /**
     * 新建核心线程池大小
     */
    public static final int NEW_CORE_POOL_SIZE = 5;
    /**
     * 新建最大线程池大小
     */
    public static final int NEW_MAX_POOL_SIZE = 10;
    /**
     * 新建阻塞任务队列大小
     */
    public static final int NEW_QUEUE_CAPACITY = 1024;

    /**
     * 创建一个线程池
     *
     * @param: task
     * @return:
     */
    public static ThreadPoolExecutor getNewThreadPool() {
        ThreadPoolExecutor exec = new ThreadPoolExecutor(
                NEW_CORE_POOL_SIZE,
                NEW_MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(NEW_QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());
        return exec;
    }

    /**
     * 禁止实例化
     */
    private ThreadPoolUtils() {

    }

    /**
     * 提交任务
     */
    public static void exec(Runnable task) {
        ThreadPoolUtils.catThreadPoolInfo(EXEC);
        EXEC.submit(task);
    }

    /**
     * 并行提交任务并获取线程执行后返回的结果
     *
     * @param: task
     * @return:
     */
    public static Future<?> execCallable(Callable<?> task) {
        ThreadPoolUtils.catThreadPoolInfo(EXEC);
        return EXEC.submit(task);
    }


    /**
     * 等待线程执行完成
     *
     * @param futures
     * @return: void
     * @date: 2022/4/2 9:49 AM
     * @author: slhero
     */
    public static void awaitThreadDone(List<Future<?>> futures) {
        futures.forEach(f -> {
            try {
                f.get(10, TimeUnit.MINUTES);
            } catch (InterruptedException | ExecutionException e) {
                logger.error("多线程等待失败:{}", e.getMessage(), e);
                throw new RuntimeException("多线程等待失败!" + e.getMessage());
            } catch (TimeoutException e) {
                logger.error("多线程等待超时:{}", e.getMessage(), e);
                throw new RuntimeException("多线程等待超时!" + e.getMessage());
            }
        });
    }
}

     因为在很多业务需要有前后关联关系,但是顺序执行有太耗时,所以这里用到了Future,等待当前业务执行完成后再进行下一步操作。

2. 问题现象

     某天发现排队阻塞线程特别多,导致某些业务操作等待很久,都没有得到返回,从后台日志看,将当前操作丢入线程后,等了20分钟才分配到线程进行计算。
线程堆积量超过两千,且持续增长达到最高4098个
     初步分析,是业务量太多,导致后台计算不过来,查看CPU使用不到10%,服务器的资源还没有完全被使用,应该不是业务操作计算堆积。

     进一步查看代码,发现代码中,存在几处类似如下的代码:

List<Future<?>> futureList = new ArrayList<>(lists.size());
for (List resultVo : lists) {
    Future<?> future = ThreadPoolUtils.execCallable(() -> {
//			do something
        return true;
    });
    futureList.add(future);
}
ThreadPoolUtils.awaitThreadDone(futureList);

     这个地方的作用是,新启线程执行业务,并等待业务返回后执行下一步操作。
本来没什么问题,但是ThreadPoolUtils.execCallable方法占用了系统默认线程池中的线程。
导致其他地方的再想使用线程时,必须等待该方法执行完成。
而该方法处于循环中,存在大量数据调用该方法,从而导致业务线程等了许久才执行。

3.解决方案

     禁止使用系统默认线程池做循环多线程等待的业务,改用新建线程池方式,最终一定要关闭线程池:

ThreadPoolExecutor pool = ThreadPoolUtils.getNewThreadPool();
try {
	List<Future<?>> futureTasks = new ArrayList<>(lists.size());
	for (List result : keys) {
		Future<?> futureTask = pool.submit(() -> {
			// do somethin
			}
			return true;
		});
		futureTasks.add(futureTask);
	}
	ThreadPoolUtils.awaitThreadDone(futureTasks);
} catch (Exception e) {
	throw new RuntimeException(e);
} finally {
	try {
		if (pool != null && !pool.isShutdown()){
			pool.shutdown();
		}
	} catch (Exception e) {
		logger.error("关闭线程池失败:{}", e.getMessage(), e);
	}
}

4.总结

     系统共用的工具类、公共方法,大量调用时一定要注意是否会影响其他业务.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值