Java线程超时控制-记录案例

本文介绍了如何在Java中利用FutureTask控制接口调用超时,并通过单独线程监控任务执行状态。示例代码展示了如何创建线程池,提交任务并设置超时时间,以及在超时时进行处理。此外,还讨论了FutureTask的isDone方法可能存在线程未完成但已返回true的问题,引用了JDK的bug报告。
摘要由CSDN通过智能技术生成

背景描述

调用关联方接口,控制自身调用的时间;理论上由接口发布方来控制调用超时时间更好,例如feign调用可以直接通过注解配置,单个接口可以设置Request控制,这里记录下调用方的控制方式
常用方式:FutureTask自带方法

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
        if (unit == null)
            throw new NullPointerException();
        int s = state;
        if (s <= COMPLETING &&
            (s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
            throw new TimeoutException();
        return report(s);
    }

与不带参数的get一样,使用时会阻塞主线程。如果对应场景有需要可以单独再启一个线程等待结果返回。
这里直接贴测试的主要代码:

public class MainThread {
    public static void main(String[] args) {
        //amend commit test03
        System.currentTimeMillis();
        ThreadPoolTaskExecutor executorThreadPool = new ThreadPoolTaskExecutor();
        ThreadPoolTaskExecutor monitorThreadPool = new ThreadPoolTaskExecutor();
        List<ThreadMonitorTask> taskList = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Apple red = new Apple("red", 200 + finalI);
            FutureTask<Apple> appleFutureTask = new FutureTask<>(() -> {
                //do something
                return red;
            });
            taskList.add(new ThreadMonitorTask<Apple>(red, appleFutureTask));
        }
        ThreadTaskExecutor executor = new ThreadTaskExecutor(taskList, executorThreadPool, monitorThreadPool);
        executor.execute();
       List<ThreadMonitorTask> retList = executor.getList();
    }
}

分析一下主要的代码:

单独启线程来获取future task的结果,近似理解为开个线程监控业务逻辑的返回值。也可以采用缓存(业务线程处理完写结果到缓存中,主线程定时去取),或者主线程不在乎等待时间,可以在主线的流程中get

public void run() {
        try {
            //默认等待时长10S
            result = futureTask.get(10, TimeUnit.SECONDS);
            sts = Boolean.TRUE;
        } catch (Exception e) {
            //对超时异常和主动打断异常做特殊处理
            if(e instanceof TimeoutException || e instanceof CancellationException){
                //do something
                errorMsg = "timeout";
            }

        } finally {
            sts = Boolean.FALSE;
            System.out.println("打印入参和结果");
        }
    }

对整体调用的时间也有控制的话,需要下面的逻辑来控制:
1.设置任务提交耗时
2.考虑超时处理
3.isDone判断及业务线程是否处理完成判断

public void execute(){
        logger.info("ThreadTaskExecutor execute start");
        long startTime = System.currentTimeMillis();
        //提交任务
        submitTask();
        while (true) {
            //监控时间
            long currentTimeMillis = System.currentTimeMillis();
            if (currentTimeMillis - startTime > timeout) {
                logger.info("total timeout");
                //总体超时
                stop();
                return;
            }

            boolean isRun = Boolean.FALSE;
            for (ThreadMonitorTask threadMonitorTask : list) {
                if (!threadMonitorTask.isDone()) {
                    //任务执行中
                    isRun = Boolean.TRUE;
                }
            }

            if (!isRun) {
                //任务执行完成
                return;
            }
        }
    }

最终 List retList = executor.getList();
返回的结果里包含执行标识,返回结果,异常信息等

自定义的处理类,完整代码如下

public class ThreadMonitorTask<T> implements Runnable {

    /**
     * 线程返回结果,根据自己定义的类型进行转换
     */
    private T result;

    /**
     * 可定义成范型,作为参数,或者要和结果进行逻辑交互的对象
     */
    private Apple apple;

    /**
     * 传入的需要进行监控的task
     */
    private FutureTask<T> futureTask;

    /**
     * 执行状态,增加标识判断对应的结果是否成功获取
     */
    private Boolean sts;

    /**
     * 预留保存异常信息
     */
    private String errorMsg;

    public ThreadMonitorTask(Apple apple, FutureTask<T> futureTask) {
        this.apple = apple;
        this.futureTask = futureTask;
    }

    public T getResult() {
        return result;
    }

    public void setResult(T result) {
        this.result = result;
    }

    public Apple getApple() {
        return apple;
    }

    public void setApple(Apple apple) {
        this.apple = apple;
    }

    public Boolean getSts() {
        return sts;
    }

    public void setSts(Boolean sts) {
        this.sts = sts;
    }

    public FutureTask<T> getFutureTask() {
        return futureTask;
    }

    public void setFutureTask(FutureTask<T> futureTask) {
        this.futureTask = futureTask;
    }

    @Override
    public void run() {
        try {
            //默认等待时长
            result = futureTask.get(10, TimeUnit.SECONDS);
            sts = Boolean.TRUE;
        } catch (Exception e) {
            //对超时异常和主动打断异常做特殊处理
            if(e instanceof TimeoutException || e instanceof CancellationException){
                //do something
                errorMsg = "timeout";
            }

        } finally {
            sts = Boolean.FALSE;
            System.out.println("打印入参和结果");
        }
    }

    /**
     * 手动打断
     */
    public void cancel(){
        futureTask.cancel(true);
    }

    /**
     * 是否执行完
     * @return
     */
    public boolean isDone(){
        return futureTask.isDone();
    }
}

执行类:

public class ThreadTaskExecutor {
    private static final Logger logger = LoggerFactory.getLogger(ThreadTaskExecutor.class);

    /**
     *监控任务
     */
    private List<ThreadMonitorTask> list;

    /**
     * 执行起限制总时间,单位默认:ms
     */
    private long timeout = 30000;

    private ThreadPoolTaskExecutor taskExecutor;

    private ThreadPoolTaskExecutor monitorExecutor;

    public ThreadTaskExecutor(List<ThreadMonitorTask> list, ThreadPoolTaskExecutor taskExecutor,
                              ThreadPoolTaskExecutor monitorExecutor) {
        this.list = list;
        this.taskExecutor = taskExecutor;
        this.monitorExecutor = monitorExecutor;
    }

    public List<ThreadMonitorTask> getList() {
        return list;
    }

    public void setList(List<ThreadMonitorTask> list) {
        this.list = list;
    }
    
    public void execute(){
        logger.info("ThreadTaskExecutor execute start");
        long startTime = System.currentTimeMillis();
        //提交任务
        submitTask();
        while (true) {
            //监控时间
            long currentTimeMillis = System.currentTimeMillis();
            if (currentTimeMillis - startTime > timeout) {
                logger.info("total timeout");
                //总体超时
                stop();
                return;
            }

            boolean isRun = Boolean.FALSE;
            for (ThreadMonitorTask threadMonitorTask : list) {
                if (!threadMonitorTask.isDone()) {
                    //任务执行中
                    isRun = Boolean.TRUE;
                }
            }

            if (!isRun) {
                //任务执行完成
                return;
            }
        }
    }
    
    private void submitTask(){
        //启动任务
        for (ThreadMonitorTask threadMonitorTask : list) {
            taskExecutor.submit(threadMonitorTask.getFutureTask());
        }
        //启动监控
        for (ThreadMonitorTask threadMonitorTask : list) {
            monitorExecutor.submit(threadMonitorTask);
        }
    }

    private void stop(){
        for (ThreadMonitorTask threadMonitorTask : list) {
            threadMonitorTask.cancel();
        }
    }
}

问题记录:FutureTask.isDone returns true when task has not yet completed
描述:
执行器通过FutureTask.isDone方法判断线程是否执行完成,但是事实上,jdk1.8比较不同版本是存在使用风险的
先看FutureTask中的get方法,等待的条件都是<=COMPLETING,状态种类如下:

    private static final int NEW          = 0;
    private static final int COMPLETING   = 1;
    private static final int NORMAL       = 2;
    private static final int EXCEPTIONAL  = 3;
    private static final int CANCELLED    = 4;
    private static final int INTERRUPTING = 5;
    private static final int INTERRUPTED  = 6;

在这里插入图片描述
但是,isDone方法如下:
在这里插入图片描述
也就是说,当状态为COMPLETING = 1的时候,是存在线程执行对应的结果尚未返回即现场尚未结束,同时isDone返回true,所以如果以此状态值判断是否执行完成是存在问题的

问题参考:https://bugs.openjdk.org/browse/JDK-8073704

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值