John Jiang

a cup of Java, cheers!
https://github.com/johnshajiang/blog

   :: 首页 ::  :: 联系 :: 聚合  :: 管理 ::
  131 随笔 :: 1 文章 :: 530 评论 :: 0 Trackbacks
Java并发基础实践--退出任务II
本系列上一篇中所述的退出并发任务的方式都是基于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());
}

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();
}
此处就是调用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();
    
}
其中,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实现不仅可以获取任务的执行结果,还可以用于取消任务的执行。
posted on 2013-10-07 16:55 John Jiang 阅读(3156) 评论(3)  编辑  收藏 所属分类: JavaConcurrency原创Java并发基础实践

评论

# re: Java并发基础实践--退出任务II(原) 2013-10-08 11:04 xdemo.cn
哪抄的?继续继续!  回复  更多评论
  

# re: Java并发基础实践--退出任务II(原) 2013-10-08 16:22 Sha Jiang
@xdemo.cn
每个字,每行代码都是我自己亲自写的:-(  回复  更多评论
  

# re: Java并发基础实践--退出任务II(原)[未登录] 2013-10-16 14:38 程序员
@Sha Jiang
厉害,期待写更多的好文  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: