java/spring boot 中断已经运行的接口

中断导入接口

一、问题

大量数据的导入功能,之前项目中做的导入都是用户选择需要导入的文件点击确定调用后端的接口,等到接口执行结束,告诉用户导入的结果(成功或者失败),这个导入过程中用户一直在当前页面等待,数据量大的时候需要两三分钟的时间,而且不能进行其他操作,这样严重影响用户的操作体检,浪费了大量时间,所以用户提出在导入的过程中可以进行其他操作,等到返回这个页面时显示导入结果,而且能够随时中断导入。

二、解决

接口在正常情况下,会一直执行下去,除非遇见了异常或者资源耗尽,才会中途停止。显然资源耗尽是很危险的,我们只能考虑人为的提前终止循环里的操作,或者人为的制造异常。

1、步骤

SpringBoot的每一次接口请求相当于新建一个线程或者用线程池管理未被使用的线程。
在处理导入的代码里面加一个信号变量,控制循环的退出。
在导入接口运行的过程中,使用额外的接口改变信号变量的值,达到退出循环的目的。
在信号变量发生变化,退出执行的时候,选择直接return 和抛出异常。
return 的话可能会在某次循环中一部分导入成功了,而一部分导入失败。
使用抛出异常加上事务控制,可以控制每一次循环成功要么成功,要么失败,记录失败的点,方便下次执行。

2、代码如下

@RestController
@RequestMapping("/interrupt")
public class InterruptController {
    
    

    // 设置的信号变量,最好使用volatile 关键字或者使用AtomicInteger原子类
    private volatile static int flag = 0;

    /**
     * 测试中断接口
     *
     * @return 出参
     * @throws Exception 异常
     */
    @GetMapping(value = "/test1")
    public String test1() throws Exception {
    
    
        for (int i = 0; i < 20; i++) {
    
    
            if (flag != 0) {
    
    
                // 信号变量发生改变,抛出异常,加上事务,保证每一次执行要么成功,要么失败
                throw new Exception("中断调用");
            }
            System.out.println("当前线程-测试中断接口:" + i + "个" + Thread.currentThread().getName());
            // 模拟执行
            Thread.sleep(3000);
        }
        return "SUCCESS";
    }

    /**
     * 修改信号变量
     *
     * @param flagInt 入参
     * @return 出参
     * @throws Exception 异常
     */
    @GetMapping(value = "/signalVariable")
    public String signalVariable(Integer flagInt) throws Exception {
    
    
        // 信号变量改为1 因为线程被异常中断,已经结束,即使信号变量变回原来的也不会再执行
        // 信号变量改为0 初始值
        flag = flagInt;
        System.out.println("当前线程-修改信号变量-02:" + Thread.currentThread().getName());
        return "SUCCESS";
    }
}

3、单接口测试

1、先调用测试接口:http://localhost:8090/interrupt/test1
测试接口
2、改变信号变量:http://localhost:8090/interrupt/signalVariable?flagInt=1
改变信号变量
3、接口报错不再执行
接口报错
接口停止
此时是只有一个接口在调用,但实际情况会有多个用户同时调用(也就是多线程),那这种情况用上述的方法还可以吗?我们来测试一下

4、多接口测试

1、调用三次接口,可以发现后台接口都在执行
多线程测试
2、改变信号变量:http://localhost:8090/interrupt/signalVariable?flagInt=1
改变信号变量
3、发现三个接口都报错且不再执行了
接口停止
报错

5、结论

在接口执行的过程中,通过另外的一个接口来改变flag 信号变量的值,使得循环抛出异常,这样达到终止接口的目的。本质上,中断接口的访问就是中断一个线程,线程中断了,即使改变信号变量也不会再次恢复到之前的执行状态。

这种方式只能用于单线程的停止或者只执行一次的接口,如:数据初始化、服务迁移等,对多线程的停止不能起到正确的控制,那我们只能寻求新的办法,如果想控制每一个接口执行或停止,用多个变量显然是不现实的,因为我们不知道会有多少个接口调用,但是每次调用都会有一个线程,那我们是不是找到这个线程,并停止这个线程就可以了呢,这样也不会影响其他线程的执行,接下来我们一起测试一下吧。

三、多线程的停止

1、线程常用方法

设置线程名称:
	构造方法:public Thread (Runnable Target,String name)
	设置名字:public final void setName(String name)
获取线程名称:
	获取**当前运行**的线程名称:Thread.currentThread().getName()
	获取**调用此方法**的线程名称:public final String getName()
中断线程
	中断调用此方法的线程:public void interrupt()

当我们调用接口的时候,可以用Thread.currentThread().getName()获取线程的名称,然后将这个名称记录到一张表里面,然前台查询,等我们需要停止的时候让前台拿着这个线程名称指定需要停止的线程,如下:

2、步骤

Spring Boot 的每一次接口请求相当于新建一个线程或者用线程池管理未被使用的线程。
在处理导入逻辑开始的代码里面获取线程的名称,然后将这个名称记录到一张表里面。
在导入接口运行的过程中,使用额外的接口中断指定的线程,达到导入停止的目的。

3、代码如下

@RestController
@RequestMapping("/interrupt")
public class InterruptController {
    
    
 
    /**
     * 测试中断接口
     *
     * @return 出参
     * @throws Exception 异常
     */
    @GetMapping(value = "/test")
    public String test() throws Exception {
    
    
        // 记录线程名称
        System.out.println("测试中断接口-当前线程名称:" + Thread.currentThread().getName());
        for (int i = 0; i < 20; i++) {
    
    
            System.out.println("第" + i + "条数据执行 业务逻辑");
            // 模拟执行
            Thread.sleep(3000);
        }
        return "SUCCESS";
    }

    /**
     * 中断线程
     *
     * @param name 入参
     * @return 出参
     */
    @GetMapping(value = "/killThreadByName")
    public boolean killThreadByName(String name) {
    
    
        // 获取所有线程
        ThreadGroup currentGroup = Thread.currentThread().getThreadGroup();
        int noThreads = currentGroup.activeCount();
        Thread[] lstThreads = new Thread[noThreads];
        currentGroup.enumerate(lstThreads);
        System.out.println("现有线程个数:" + noThreads);

        for (int i = 0; i < noThreads; i++) {
    
    
            String threadName = lstThreads[i].getName();
            System.out.println("第" + i + "个线程名为:" + threadName);
            if (threadName.equals(name)) {
    
    
                System.out.println("中断线程:" + name);
                lstThreads[i].interrupt();
                return true;
            }
        }
        return false;
    }
}

4、测试

1、调用四个接口
调用接口
2、中断其中一个接口
中断接口
暂停其中一个接口
3、其他线程正常运行
正常运行

5、结论

在接口执行的过程中,通过记录线程名字,使得导入抛出异常,这样达到终止接口的目的。本质上,中断接口的访问就是中断一个线程,指定线程名称,这样就不会影响其他导入的执行状态。

参考链接:中断接口
参考链接:根据线程名称获取线程、停止线程

猜你喜欢

转载自blog.csdn.net/qq_42547733/article/details/128675593
今日推荐