Java并发基本实践:退出任务II
当前位置:以往代写 > JAVA 教程 >Java并发基本实践:退出任务II
2019-06-14

Java并发基本实践:退出任务II

Java并发基本实践:退出任务II

副标题#e#

在本系列的上一篇中所述的退出并发任务的方法都是基于JDK 5之前的API,本文将先容利用由JDK 5引入的并发东西包中的API来退出任务。(2013.10.08最后更新)

在本系列的前一篇中报告了三种退出并发任务的方法–遏制线程;可打消的任务;间断,但都是基于JDK 5之前的API。本篇将先容由JDK 5引入的java.concurrent包中的Future来打消任务的执行。

1. Future模式

Future是并发编程中的一种常见设计模式,它相当于是Proxy模式与Thread-Per-Message模式的团结。即,每次都建设一个单独的线程去执行一个耗时的任务,而且建设一个Future工具去持有实际的任务工具,在未来需要的时候再去获取实际任务的执行功效。
依然先建设一个用于扫描文件的任务FileScannerTask,如代码清单1所示,

清单1
public class FileScannerTask implements Runnable {
    
    private File root = null;
    
    private ArrayList<String> filePaths = new ArrayList<String>();
    
    public FileScannerTask(File root) {
        if (root == null || !root.exists() || !root.isDirectory()) {
            throw new IllegalArgumentException("root must be directory");
        }
    
        this.root = root;
    }
    
    @Override
    public void run() {
        travleFiles(root);
    }
    
    private void travleFiles(File parent) {
        String filePath = parent.getAbsolutePath();
        filePaths.add(filePath);
    
        if (parent.isDirectory()) {
            File[] children = parent.listFiles();
            if (children != null) {
                for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }
    
    public List<String> getFilePaths() {
        return (List<String>) filePaths.clone();
    }
}

此处的文件扫描任务,提供了一个getFilePaths()要领以答允随时都可以取出当前已扫描过的文件的路径(相当于一个任务快照)。然后,建设一个针对该任务的Future类,如代码清单2所示,

清单2
public class FileScannerFuture {
    
    private FileScannerTask task = null;
    
    public FileScannerFuture(FileScannerTask task) {
        new Thread(task).start();
        this.task = task;
    }
    
    public List<String> getResult() {
        return task.getFilePaths();
    }
}

FileScannerFuture持有FileScannerTask的引用,并建设一个独立的线程来执行该任务。在任务的执行进程中,应用措施可以在"将来"的某个时刻去获取一个任务的快照,如代码清单3所示,

清单3
public static void main(String[] args) throws Exception {
    FileScannerFuture future = new FileScannerFuture(new FileScannerTask(new File("C:")));
    
    TimeUnit.SECONDS.sleep(1);
    List<String> filePaths1 = future.getResult();
    System.out.println(filePaths1.size());
    
    TimeUnit.SECONDS.sleep(1);
    List<String> filePaths2 = future.getResult();
    System.out.println(filePaths2.size());
}


#p#副标题#e#

2. 利用并发东西包中的Future实现

前面所展示的Future实现十分的简略,没有实际应用的意义。利用FileScannerFuture,应用措施在获取filePaths时,无法得知其获取的是否为最终功效,即无法判定FileScannerTask是否已经完成。并且,也不能在须要时遏制FileScannerTask的执行。毫无疑问,由JDK 5引入的并发东西包必定会提供此类实用东西,如FutureTask。为了利用并发东西包中的Future,需要修改前述的FileScannerTask实现,让其实现Callable接口,如代码清单4所示,

清单4
public class FileScannerTask implements Callable<List<String>> {
    
    private File root = null;
    
    private List<String> filePaths = new ArrayList<String>();
    
    public FileScannerTask(File root) {
        if (root == null || !root.exists() || !root.isDirectory()) {
            throw new IllegalArgumentException("root must be directory");
        }
    
        this.root = root;
    }
    
    @Override
    public List<String> call() {
        travleFiles(root);
        return filePaths;
    }
    
    private void travleFiles(File parent) {
        String filePath = parent.getAbsolutePath();
        filePaths.add(filePath);
    
        if (parent.isDirectory()) {
            File[] children = parent.listFiles();
            if (children != null) {
                for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }
    
    public List<String> getFilePaths() {
        return (List<String>) filePaths.clone();
    }
}

应用措施也要相应的修改成如代码清单5所示,利用ExecutorService来提交任务,并建设一个Future/FutureTask实例。

清单5
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));
    
    try {
        List<String> filePaths = future.get();
        System.out.println(filePaths.size());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    
    executorService.shutdown();
}

查察本栏目

#p#分页标题#e#

此处就是挪用Future.get()要领来获取任务的执行功效,假如任务没有执行完毕,那么该要领将会被阻塞。该Future实现的长处就是,正常环境下,只有在任务执行完毕之后才气获取其功效,以担保该功效是最终执行功效。

3. 利用Future打消任务

Future除了界说有可获取执行功效的get要领(get()以及get(long timeout, TimeUnit unit)),还界说了三个要领:cancel(),isCancelled()以及isDone(),用于打消任务,以及鉴定任务是否已被打消、已执行完毕。如代码清单6所示,

清单6
public interface Future<V> {
    
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    boolean isDone();
        
}

#p#副标题#e#

个中,cancel()要领中的boolean参数若为true,暗示在打消该任务时,若执行该任务的线程仍在运行中,则对其举办间断。如代码清单7所示,若任务执行超时了,那么就打消它。

清单7
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    Future<List<String>> future = executorService.submit(new FileScannerTask(new File("C:")));
    
    try {
        List<String> filePaths = future.get(1, TimeUnit.SECONDS);
        System.out.println(filePaths.size());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (TimeoutException e) {
        e.printStackTrace();
    } finally {
        future.cancel(true);
    }
    
    executorService.shutdown();
}

在实际应用中,打消任务的原由必定不只仅只是超时这么简朴,还大概是由于接管到了用户的指令。此时,则大概会从另一个独立线程去打消该任务。除了打消任务之外,有时还需要取出任务中已经生成的部门功效。但为了可以或许响应任务的退出,首先需要修改FileScannerTask,使恰当任务被打消(间断)时,任务可以或许真正的快速遏制并返回,如代码清单8所示,

清单8
public class FileScannerTask implements Callable<List<String>> {
    
        
    
    private void travleFiles(File parent) {
        if (Thread.currentThread().isInterrupted()) {
            return;
        }
    
        String filePath = parent.getAbsolutePath();
        filePaths.add(filePath);
    
        if (parent.isDirectory()) {
            File[] children = parent.listFiles();
            if (children != null) {
                for (File child : children) {
                    travleFiles(child);
                }
            }
        }
    }
    
        
}

相应地修改应用措施的代码,如代码清单9所示,

清单9
public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    FileScannerTask task = new FileScannerTask(new File("C:"));
    final Future<List<String>> future = executorService.submit(task);
        
    new Thread(new Runnable() {
            
        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            future.cancel(true);
        }
    }).start();
        
    try {
        List<String> filePaths = future.get();
        System.out.println(filePaths.size());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    } catch (CancellationException e) {
        List<String> filePaths = task.getFilePaths();
        System.out.println("Partly result: " + filePaths.size());
    }
        
    executorService.shutdown();
}

由上可知,此处利用Future.cancel(true)的本质依然是操作了线程的间断机制。

4. 小结

利用Future可以在任务启动之后的特按机缘再去获取任务的执行功效。由JDK 5引入的并发东西包中提供的Future实现不只可以获取任务的执行功效,还可以用于打消任务的执行。

    关键字:

在线提交作业