Future任务机制: Fork/Join 框架
Future任务机制和FutureTask
化繁为简,分而治之,递归的分解和合并,直到任务小到可以接受的程度。
Fork/Join 框架是Java7提供的一个用于并行执行任务的框架。
是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。
Fork 就是把大任务切分为若干子任务并行的执行
Join 就是合并这些子任务的执行结果,最后得到这个大任务的结果。
1 public interface Callable<V> { 2 3 // 只有一个获得返回结果的方法,实现这个方法即可 4 5 V call() throws Exception; 6 }
Future类就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。
必要时,通过get方法获取执行结果,该方法会阻塞直到任务返回结果。
package路径:java.util.concurrent。也是一个接口。
1 public interface Future<V> { 2 3 // 1. 如果取消成功则返回true, 取消失败则返回false 4 // 2. mayInterruptIfRunning 表示是否允许取消正在执行却没有完毕的任务,如果设置true, 则取消。 5 // 3. 如果任务完成, mayInterruptIfRunning为true,还是false, 都返回false。 6 // 4. 如果取消已经完成的任务返回false 7 // 5. 如果任务正在执行,若mayInterruptIfRunning设置为true, 则返回true。若mayInterruptIfRunning设置为false, 则返回false。 8 // 6. 如果任务还没有执行,则无论mayInterruptIfRunning设置为true还是false, 肯定返回true 9 boolean cancel(boolean mayInterruptIfRunning); 10 11 // 表示任务是否被取消成功,如果在任务正常完成前被取消,则返回ture 12 boolean isCanceled(); 13 14 // 表示任务是否已经完成, 若任务完成, 则返回true。 15 boolean isDone(); 16 17 // 用来获取执行结果, 这个方法会产生阻塞, 会一直等到任务执行完毕才返回 18 V get() throws InterruptedException, ExecutionException; 19 20 // 用来获取执行结果, 如果在指定时间内, 还没获取到结果, 就直接返回null 21 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; 22 }
Future 提供了三种功能:
(1)判断任务是否完成
(2)能够中断任务
(3)能够获取任务执行结果
FutureTask是Future接口的一个唯一实现类。
public class FutureTask implements RunnableFuture<V> { } // RunnableFuture 接口的实现 public interface RunnableFuture <V>extends Runnable, Future<V> { void run(); }
FutureTask实现了RunnableFuture,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callback的返回值。
两个构造函数:
1 public FutureTask(Callable<V> callable) { 2 // 创建一个FutureTask, 一旦运行就执行给定的Callback。 3 } 4 5 public FutureTask(Runnable runnable, V result) { 6 // 创建一个FutureTask, 一旦运行就执行给定的runnable, 并安排成功完成时,get返回给定的结果。 7 }
接下来的Fork/Join就是基于Future实现的:
1 class demo { 2 3 public static void main(String[] args) throws InterruptedException, ExecutionException { 4 5 FoundTask task1 = new FoundTask("Thread Found Name"); 6 FutureTask<String> f1 = new FutureTask<String>(task1); 7 8 new Thread(f1).start(); 9 10 System.out.printf(f1.get()); 11 12 FutureTask<Integer> f2 = new FutureTask<Integer>(new FoundRun(), 2); 13 14 new Thread(f2).start(); 15 System.out.printf("result-" + f2.get()); 16 } 17 } 18 19 class FoundTask implements Callable<String> { 20 21 private String mName = null; 22 23 FoundTask(String name) { 24 mName = name; 25 } 26 27 @Override 28 public String call() throws Exception { 29 30 Thread.sleep(1000); 31 32 System.out.printf(mName + "finish the task"); 33 34 return "result-1"; 35 } 36 } 37 38 class FoundRun implements Runnable { 39 40 @Override 41 public void run() { 42 43 try { 44 Thread.sleep(1000); 45 } catch (InterruptedException e) { 46 e.printStackTrace(); 47 } 48 49 System.out.printf("FoundRun finish"); 50 } 51 }
运行结果:
1 Thread Found Name finish the task 2 result-1 3 FoundRun finish 4 result-2
按照预想,按顺序,按步骤的执行。
使用Fork/Join框架,首先要考虑到的是如何分割任务:
e.g.
1 class demo { 2 3 public static void main(String[] args) throws InterruptedException, ExecutionException { 4 5 ForkJoinPool forkJoinPool = new ForkJoinPool(); 6 7 CountTask task = new CountTask(1, 5); 8 9 Future<Integer> result = forkJoinPool.submit(task); 10 11 System.out.printf("1-5最终相加的结果:" + result.get()); 12 13 CountTask task2 = new CountTask(1, 100); 14 15 Future<Integer> result2 = forkJoinPool.submit(task2); 16 17 System.out.printf("1-100最终相加的结果:" + result2.get()); 18 19 // end 20 } 21 } 22 23 class CountTask extends RecursiveTask<Integer> { 24 25 private static final long serialVersionUID = 3336021421713606929L; 26 27 private static int splitSize = 2; 28 private int mStart, mEnd; 29 30 public CountTask(int start, int end) { 31 mStart = start; 32 mEnd = end; 33 } 34 35 36 @Override 37 protected Integer compute() { 38 int sum = 0; 39 // 如果任务已经不需要再拆分了就开始计算。 40 boolean canCompute = (mEnd - mStart) <= splitSize; 41 42 if (canCompute) { 43 44 for (int i = mStart; i <= mEnd; i++) { 45 46 sum = sum + i; 47 } 48 } else { 49 50 // 拆分两个字任务。 51 int middle = (mStart - mEnd) / 2; 52 CountTask firstTask = new CountTask(mStart, middle); 53 CountTask secondTask = new CountTask(middle + 1, mEnd); 54 55 firstTask.fork(); // 开始执行 56 secondTask.fork(); 57 58 int firstResult = firstTask.join(); // 获得第一个子任务结果,得不到结果,此线程不会往下面执行。 59 int secondResult = secondTask.join(); 60 61 // 合并两个子任务的结果。 62 sum = firstResult + secondResult; 63 } 64 65 return sum; 66 } 67 }
运行结果:
1 1-5最终相加的结果:15 2 1-100最终相加的结果::5050
Fork/Join模式优缺点及应用场景。
注意:分拆的对象过多时,小心一下子把内存撑满了,等待线程的CPU资源释放了,但是线程对象等待时,不会被垃圾机制回收。
场景:
对于树形结构类型的数据的处理和遍历非常适合。
e.g. 我们要对一个静态资源服务器的图片文件目录进行遍历和分析的时候,我们需要递归的统计每个目录下的文件数量,最后汇总,非常适合用分叉/结合框架来处理。
1 class demo { 2 3 public static void main(String[] args) throws InterruptedException, ExecutionException { 4 5 Integer count = new ForkJoinPool().invoke(new CountingTask(Paths.get("D://fish"))); 6 7 System.out.printf("D:盘fish下面总文件数量:" + count); 8 9 // end 10 } 11 } 12 13 // 处理单个目录的任务 14 class CountingTask extends RecursiveTask<Integer> { 15 16 private Path mDir; 17 public CountingTask (Path dir) { 18 mDir = dir; 19 } 20 21 22 @Override 23 protected Integer compute() { 24 25 int count = 0; 26 List<CountingTask> subTasks = new ArrayList<>(); 27 28 // 读取目录dir的子路径。 29 try { 30 31 DirectoryStream<Path> ds = Files.newDirectoryStream(dir); 32 33 for (Path subPath : ds) { 34 35 if (Files.isDirectory(subPath, LinkOption.NOFOLLOW_LINKS)) { 36 37 // 对每个子目录都新建一个子任务 38 subTasks.add(new CountingTask(subPath)); 39 } else { 40 41 // 遇到文件,则计数器增加1 42 count ++; 43 } 44 } 45 46 if (!subTasks.isEmpty()) { 47 // 在当前的ForkJoinPool上调度所有的子任务 48 for (CountingTask subTask : invokeAll(subTasks)) { 49 50 count += subTask.join(); 51 } 52 } 53 54 } catch (IOException ex) { 55 return 0; 56 } 57 } 58 }
运行结果:运算速度还是非常快的,但是一旦文件多了,也是非常耗资源的,电脑就会出现卡顿的情况
1 D:盘fish下面总文件数量:7647
更多线程安全链接: