Java小技巧:利用异常中断当前任务

Java小技巧:利用异常中断当前任务

在日常开发中,我们经常遇到调用别人的代码来完成某个任务,但是当代码比较耗时的时候,没法从外部终止该任务。有些程序从开始就考虑到了这个场景,就会提供对应的cancelstop之类的方法用于终止任务,但还是会有很多三方库并没有提供响应接口。比如,下面这个下载示例:

java 复制代码
class DownloadTask {
    public void download(Listener listener) {
        try {
            for (int i = 0; i < 100; i++) {//模拟下载过程
                Thread.sleep(1000);
                listener.onProgressUpdated(i + 1);
            }

            listener.onCompleted();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    public interface Listener {
        void onProgressUpdated(int progress);

        void onCompleted();
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        DownloadTask task = new DownloadTask();
        task.download(new DownloadTask.Listener() {
            @Override
            public void onProgressUpdated(int progress) {
                System.out.println("onProgressUpdated: " + progress);
                if (isInterrupted.get()) {
                    throw new EndException();
                }
            }

            @Override
            public void onCompleted() {

            }
        });
    }
}

这里的DownloadTask开始运行后,就无法直接停止。如果DownloadTask是第三方库的代码时,我们可能不方便改它的代码使其支持中断。本文介绍一种利用异常来终止这样的任务的方法。

(注:本文介绍的方法仅限于单线程或单个子线程的情况,并且需要有在任务运行的线程执行代码的能力,比如回调)

场景1:任务没有另起线程的情况

这个就是上面展示的那个下载示例,我们可以通过回调,直接利用异常中断该任务:

java 复制代码
//DownloadTask 同上

public class Main {
    private static volatile boolean isInterrupted;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {//模拟外部调用,5秒后通过设置isInterrupted来取消任务,也可以把这段代码放到一个新的cancel方法中
            sleep(5000);
            isInterrupted = true;
        }).start();

        try {
            DownloadTask task = new DownloadTask();
            task.download(new DownloadTask.Listener() {
                @Override
                public void onProgressUpdated(int progress) {
                    System.out.println("onProgressUpdated: " + progress);
                    if (isInterrupted) {
                        throw new EndException();
                    }
                }

                @Override
                public void onCompleted() {
                    System.out.println("onCompleted...");
                }
            });
        }catch (EndException e){
            System.out.println("end...");
            //is interrupted, ignore
        }

        Thread.currentThread().join();
    }

    static class EndException extends RuntimeException {

    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

场景2:任务另起子线程的情况

如果任务另起子线程来执行,那么就没法直接用try-catch来捕获异常,那么就得通过线程的UncaughtExceptionHandler来完成。假如下载的代码是这样的:

java 复制代码
public class DownloadTask {
    public void download(Listener listener) {
        new Thread(() -> {
            try {
                for (int i = 0; i < 100; i++) {//模拟下载过程
                    Thread.sleep(1000);
                    listener.onProgressUpdated(i + 1);
                }

                listener.onCompleted();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
    }

    public interface Listener {
        void onProgressUpdated(int progress);

        void onCompleted();
    }
}

那么外部调用并终止的代码就是:

java 复制代码
public class Main {
    private static volatile boolean isInterrupted;

    public static void main(String[] args) throws InterruptedException {
        Thread.UncaughtExceptionHandler handler = (t, e) -> {
            if(e instanceof EndException){
                System.out.println("end...");
                //is interrupted, ignore
            }
        };

        DownloadTask task = new DownloadTask();
        task.download(new DownloadTask.Listener() {
            @Override
            public void onProgressUpdated(int progress) {
                System.out.println("onProgressUpdated: " + progress);
                if(Thread.currentThread().getUncaughtExceptionHandler() != handler){//注意这里
                    Thread.currentThread().setUncaughtExceptionHandler(handler);
                }
                if (isInterrupted) {
                    throw new EndException();
                }
            }

            @Override
            public void onCompleted() {
                System.out.println("onCompleted...");
            }
        });

        sleep(5000);
        isInterrupted = true;

        Thread.currentThread().join();
    }

    public static class EndException extends RuntimeException {

    }

    public static void sleep(long time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

写在最后

大家也能看到,这并不是什么万能的方法,甚至有很大局限,但碰到合适的场景还是可以通过简短的代码来解决问题。

相关推荐
androidwork8 分钟前
在Kotlin中绕过泛型类型擦除的实战指南
android·kotlin
androidwork12 分钟前
Kotlin全栈工程师转型路径
android·开发语言·kotlin
程序员Bears1 小时前
SSM整合:Spring+SpringMVC+MyBatis完美融合实战指南
java·spring·mybatis
liuyang-neu3 小时前
黑马点评双拦截器和Threadlocal实现原理
java
csdn_aspnet4 小时前
Java 程序求圆弧段的面积(Program to find area of a Circular Segment)
java·开发语言
Estar.Lee5 小时前
如何使用PHP创建一个安全的用户注册表单,包含输入验证、数据过滤和结果反馈教程。
android·安全·php
Magnum Lehar5 小时前
vulkan游戏引擎vulkan部分的fence实现
java·前端·游戏引擎
on the way 1235 小时前
创建型模式之Factory Method(工厂方法)
android·java·工厂方法模式
无心水5 小时前
【后端高阶面经:MongoDB篇】41、MongoDB 是怎么做到高可用的?
java·开发语言·mongodb·java面试·高可用·后端高阶面经·后端工程师的高阶面经
无心水6 小时前
【后端高阶面经:MongoDB篇】40、怎么优化MongoDB的查询性能?
java·开发语言·mongodb·java面试·后端高阶面经·后端工程师的高阶面经·java高阶面经