• 使用Future停止超时任务


    今天学了下多线程中超时任务的处理,这里和大家分享下,遇到了点问题没能解决,留下来希望大家帮我解疑啊。

    在JAVA中停止线程的方法有多种,有一种是结合ExecutorService和Future的使用,停止在线程池中超时的任务。
    这种情况下处理的都是比较耗时的操作,比如请求资源,数据库查询等,当超过一定时间没有返回结果,就结束线程,提高响应速度。

    具体步骤如下:

    1. 实现Runnable接口或者Callable接口,分别实现run方法或者call方法,在方法中执行耗时操作。
    2. 将步骤1中的耗时操作线程放入到线程池中(通过ExecutorService.submit方法),这个线程池由ExecutorService来管理。
    3. 步骤2中的submit方法会返回一个Future对象,这个Future对象得到耗时操作的执行结果。Future的get(long timeout, TimeUnit unit)将会在指定的时间内去获得执行结果,如果操作还没执行完,就会抛出TimeoutException,在捕获这个超时异常时就可以取消耗时任务的执行(通过Future.cancel(true)).

    下面是一个下载大文件的程序,如果下载任务超时将关闭任务。
    (DownloadTask.java: 下载任务)

     1 class DownloadTask implements Runnable {
     2     private String filename;
     3     // 接收在run方法中捕获的异常,然后自定义方法抛出异常
     4     private Throwable exception;
     5     //是否关闭此下载任务
     6     private boolean isStop = false;
     7     
     8     public void setStop(boolean isStop) {
     9         this.isStop = isStop;
    10     }
    11 
    12     public DownloadTask(String filename) {
    13         this.filename = filename;
    14     }
    15 
    16     /**
    17      * 下载大数据
    18      * 
    19      * @param filename
    20      * @throws FileNotFoundException
    21      *             , IOException
    22      */
    23     private void download() throws FileNotFoundException, IOException {
    24         File file = new File(filename);
    25         File saveFile = null;
    26         String[] names = filename.split("/");
    27         String saveName = names[names.length - 1];
    28         saveFile = new File("tmp/" + saveName);
    29         InputStream input = new FileInputStream(file);
    30         OutputStream output = new FileOutputStream(saveFile);
    31 
    32         // 进行转存
    33         int len = 0;
    34         byte[] buffer = new byte[1024];
    35         while (-1 != (len = input.read(buffer, 0, buffer.length))) {
    36             if(isStop)
    37                 break;
    38             output.write(buffer, 0, len);
    39         }
    40 
    41         input.close();
    42         output.close();
    43     }
    44 
    45     public void throwException() throws FileNotFoundException, IOException {
    46         if (exception instanceof FileNotFoundException)
    47             throw (FileNotFoundException) exception;
    48         if (exception instanceof IOException)
    49             throw (IOException) exception;
    50     }
    51 
    52     @Override
    53     public void run() {
    54         try {
    55             download();
    56         } catch (FileNotFoundException e) {
    57             exception = e;
    58         } catch (IOException e) {
    59             exception = e;
    60         }
    61     }
    62 }

     注意到第4行定义了一个Throwable对象,这是因为在线程类的run方法中是没办法抛出异常的,要让外部获得程序的运行状况,就通过手动赋值和抛出,第45~49行定义了抛出异常的方法。(任务的业务逻辑不应该在内部处理掉,而应该在外部判断进行控制,个人理解)

    (LoadSomething.java: 启动下载任务和处理异常)

     1 /**
     2  * 使用Futrue来取消任务,模拟下载文件超时时取消任务
     3  * 
     4  * @author hongjie
     5  * 
     6  */
     7 public class LoadSomething {
     8     // 线程池服务接口
     9     private ExecutorService executor;
    10 
    11     
    12     public LoadSomething() {
    13         executor = Executors.newSingleThreadExecutor();
    14     }
    15 
    16     public void beginToLoad(DownloadTask task, long timeout,
    17             TimeUnit timeType) {
    18         Future<?> future = executor.submit(task);
    19         try {
    20             future.get(timeout, timeType);
    21             task.throwException();
    22         } catch (InterruptedException e) {
    23             System.out.println("下载任务已经取消");
    24         } catch (ExecutionException e) {
    25             System.out.println("下载中发生错误,请重新下载");
    26         } catch (TimeoutException e) {
    27             System.out.println("下载超时,请更换下载点");
    28         } catch (FileNotFoundException e) {
    29             System.out.println("请求资源找不到");
    30         } catch (IOException e) {
    31             System.out.println("数据流出错");
    32         } finally {
    33             task.setStop(true);
    34             // 因为这里的下载测试不用得到返回结果,取消任务不会影响结果
    35             future.cancel(true);
    36         }
    37     }
    38 }

    第18行将下载任务放入了线程池中并且开始执行下载任务,第20行中在制定时间内去获得结果,如果超时将会抛出TimeoutException, 可以看到21行是抛出下载任务中所可能遇到的异常。最后在finally块中,调用future.cancle停掉任务。这里不用ExecutorService.shutdown方法是因为可能在线程池中有其他线程正在执行任务。
    我遇到的问题:在33行中我加了个task.setStop(true)方法,这里通过改变控制变量状态来停止下载任务的执行,这种方法是可以停止掉下载任务。但是如果不用这种改变控制变量的方法,用future.cancel方法应该是能停掉下载任务的。但实际执行结果是抛出了超时异常,future.cancel返回了true,即任务已取消,但文件还是会继续下载下来,我想会不会是这样,文件下载是IO阻塞的,当下载任务执行的时候,线程会阻塞在那里,所以关不掉??希望知道的朋友指点一下我啊。

    (LoadTest.java: 测试主函数)

    1 public class LoadTest {
    2     public static void main(String[] args) {
    3         LoadSomething load = new LoadSomething();
    4         DownloadTask downloadTask = new DownloadTask("G:/Games/5211install.exe");
    5         //开始下载,并设定超时限额为3毫秒
    6         load.beginToLoad(downloadTask, 3, TimeUnit.MILLISECONDS);
    7     }
    8 }

     

    程序运行结果:
    源文件:

    抛出异常:

    目标文件:

    之前对相对路径和绝对路径不是很了解,今天查了一下,有几篇好文章,个人觉得挺不错,也和大家分享下:
    JAVA中的相对路径和绝对路径(和虚拟机JVM有关)

    问题我已经找到答案了:
    在java库中,许多可阻塞的方法都是通过提前返回或者抛出InterruptedException来响应中断请求的,从而使开发人员更容易构建出能响应取消请求的任务。然而并非所有的可阻塞方法或者阻塞机制都能响应中断;如果一个线程由于执行同步的Socket I/O或者等待获得内置锁而阻塞,那么中断请求只能设置线程的中断状态,除此之外没有其他任何作用。

    在服务应用程序中,最常见的阻塞I/O形式就是对套接字进行读取和写入。虽然InputStream和fOutputStream中的read和write等方法都不会响应中断,但通过关闭底层的套接字,可以使得由于执行read和write等方法而被阻塞的线程抛出一个socketException。

    上面的情况和这个是类似的。

     

    一颗平常心,踏踏实实,平静对待一切
  • 相关阅读:
    每日vim插件--vim中的文本对象及相关插件
    《android传感器高级编程》译者序
    我在用的mac软件(3)-效率篇
    终端环境之tmux
    我在用的mac软件(2)-终端环境之zsh和z(*nix都适用)
    我在用的mac软件(1)--终端环境之iTerm2
    转:微服务架构的理论基础
    怎么使用阿里云直播服务应用到现在主流直播平台中
    WindowsServer2012显示计算机的方法
    在Windows Server 2012启用或关闭Internet Explorer增强的安全配置
  • 原文地址:https://www.cnblogs.com/hanyuan/p/2952229.html
Copyright © 2020-2023  润新知