Java 7 Fork/Join 并行计算框架概览

本文介绍了Fork/Join框架的基本原理及应用场景,通过两个示例详细解释了如何使用Fork/Join框架进行任务的并行拆分和计算。文章还讨论了Fork/Join框架的主要类及其使用方法。

Fork/Join框架

    依靠应用程序本身并行拆封任务,如果使用简单的多线程程序的方法,复杂度必然很大。这就需要一个更好的范式或者工具来代程序员处理这类问题。Java 7也意识到了这个问题,才标准库中集成了由Doug Lea开发的Fork/Join并行计算框架。通过使用 Fork/Join 模式,软件开发人员能够方便地利用多核平台的计算能力。尽管还没有做到对软件开发人员完全透明,Fork/Join 模式已经极大地简化了编写并发程序的琐碎工作。对于符合 Fork/Join 模式的应用,软件开发人员不再需要处理各种并行相关事务,例如同步、通信等,以难以调试而闻名的死锁和 data race 等错误也就不会出现,提升了思考问题的层次。你可以把 Fork/Join 模式看作并行版本的 Divide and Conquer 策略,仅仅关注如何划分任务和组合中间结果,将剩下的事情丢给 Fork/Join 框架。但是Fork/Join并行计算框架,并不是银弹,并不能解决所有应用程序在超多核心处理器上的并发问题。

    如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决。其原理如下图。

    应用程序开发者需要做的就是拆分任务并组合每个子任务的中间结果,而不用再考虑线程和锁的问题。

一个简单的例子

我们首先看一个简单的Fork/Join的任务定义。

Java代码   收藏代码
  1. public class Calculator extends RecursiveTask<Integer> {  
  2.   
  3.     private static final int THRESHOLD = 100;  
  4.     private int start;  
  5.     private int end;  
  6.   
  7.     public Calculator(int start, int end) {  
  8.         this.start = start;  
  9.         this.end = end;  
  10.     }  
  11.   
  12.     @Override  
  13.     protected Integer compute() {  
  14.         int sum = 0;  
  15.         if((start - end) < THRESHOLD){  
  16.             for(int i = start; i< end;i++){  
  17.                 sum += i;  
  18.             }  
  19.         }else{  
  20.             int middle = (start + end) /2;  
  21.             Calculator left = new Calculator(start, middle);  
  22.             Calculator right = new Calculator(middle + 1, end);  
  23.             left.fork();  
  24.             right.fork();  
  25.   
  26.             sum = left.join() + right.join();  
  27.         }  
  28.         return sum;  
  29.     }  
  30.   
  31. }  
 

    这段代码中,定义了一个累加的任务,在compute方法中,判断当前的计算范围是否小于一个值,如果是则计算,如果没有,就把任务拆分为连个子任务,并合并连个子任务的中间结果。程序递归的完成了任务拆分和计算。

    任务定义之后就是执行任务,Fork/Join提供一个和Executor框架的扩展线程池来执行任务。

Java代码   收藏代码
  1. @Test  
  2. public void run() throws Exception{  
  3.     ForkJoinPool forkJoinPool = new ForkJoinPool();  
  4.     Future<Integer> result = forkJoinPool.submit(new Calculator(010000));  
  5.   
  6.     assertEquals(new Integer(49995000), result.get());  
  7. }  
 

Fork/Join框架的主要类


RecursiveAction供不需要返回值的任务继续。

RecursiveTask通过泛型参数设置计算的返回值类型。

ForkJoinPool提供了一系列的submit方法,计算任务。ForkJoinPool默认的线程数通过Runtime.availableProcessors()获得,因为在计算密集型的任务中,获得多于处理性核心数的线程并不能获得更多性能提升。

public <T> ForkJoinTask<T> submit(ForkJoinTask<T> task) {
    doSubmit(task);
    return task;
}

sumit方法返回了task本身,ForkJoinTask实现了Future接口,所以可以通过它等待获得结果。

另一例子

这个例子并行排序数组,不需要返回结果,所以继承了RecursiveAction。

Java代码   收藏代码
  1. public class SortTask extends RecursiveAction {  
  2.     final long[] array;  
  3.     final int start;  
  4.     final int end;  
  5.     private int THRESHOLD = 100//For demo only  
  6.   
  7.     public SortTask(long[] array) {  
  8.         this.array = array;  
  9.         this.start = 0;  
  10.         this.end = array.length - 1;  
  11.     }  
  12.   
  13.     public SortTask(long[] array, int start, int end) {  
  14.         this.array = array;  
  15.         this.start = start;  
  16.         this.end = end;  
  17.     }  
  18.   
  19.     protected void compute() {  
  20.         if (end - start < THRESHOLD)  
  21.             sequentiallySort(array, start, end);  
  22.         else {  
  23.             int pivot = partition(array, start, end);  
  24.             new SortTask(array, start, pivot - 1).fork();  
  25.             new SortTask(array, pivot + 1, end).fork();  
  26.         }  
  27.     }  
  28.   
  29.     private int partition(long[] array, int start, int end) {  
  30.         long x = array[end];  
  31.         int i = start - 1;  
  32.         for (int j = start; j < end; j++) {  
  33.             if (array[j] <= x) {  
  34.                 i++;  
  35.                 swap(array, i, j);  
  36.             }  
  37.         }  
  38.         swap(array, i + 1, end);  
  39.         return i + 1;  
  40.     }  
  41.   
  42.     private void swap(long[] array, int i, int j) {  
  43.         if (i != j) {  
  44.             long temp = array[i];  
  45.             array[i] = array[j];  
  46.             array[j] = temp;  
  47.         }  
  48.     }  
  49.   
  50.     private void sequentiallySort(long[] array, int lo, int hi) {  
  51.         Arrays.sort(array, lo, hi + 1);  
  52.     }  
  53. }  
 
Java代码   收藏代码
  1. @Test  
  2. public void run() throws InterruptedException {  
  3.     ForkJoinPool forkJoinPool = new ForkJoinPool();  
  4.     Random rnd = new Random();  
  5.     long[] array = new long[SIZE];  
  6.     for (int i = 0; i < SIZE; i++) {  
  7.         array[i] = rnd.nextInt();  
  8.     }  
  9.     forkJoinPool.submit(new SortTask(array));  
  10.   
  11.     forkJoinPool.shutdown();  
  12.     forkJoinPool.awaitTermination(1000, TimeUnit.SECONDS);  
  13.   
  14.     for (int i = 1; i < SIZE; i++) {  
  15.         assertTrue(array[i - 1] < array[i]);  
  16.     }  

  Fourinone(中文名字“四不像”)是一个四合一分布式计算框架,在写这个框架之前,我对分布式计算进行了长时间的思考,也看了老外写的其他开源框架,当我们把复杂的hadoop当作一门学科学习时,似乎忘记了我们想解决问题的初衷:我们仅仅是想写个程序把几台甚至更多的机器一起用起来计算,把更多的cpu和内存利用上,来解决我们数量大和计算复杂的问题,当然这个过程中要考虑到分布式的协同和故障处理。如果仅仅是为了实现这个简单的初衷,为什么一切会那么复杂,我觉的自己可以写一个更简单的东西,它不需要过度设计,只需要看上去更酷一点,更小巧一点,功能更强一点。于是我将自己对分布式的理解融入到这个框架中,考虑到底层实现技术的相似性,我将Hadoop,Zookeeper,MQ,分布式缓存四大主要的分布式计算功能合为一个框架内,对复杂的分布式计算应用进行了大量简化和归纳。   首先,对分布式协同方面,它实现了Zookeeper所有的功能,并且做了很多改进,包括简化Zookeeper的树型结构,用domain/node两层结构取代,简化Watch回调多线程等待编程模型,用更直观的容易保证业务逻辑完整性的内容变化事件以及状态轮循取代,Zookeeper只能存储信息不大于1M的内容,Fourinone超过1M的内容会以内存隐射文件存储,增强了它的存储功能,简化了Zookeeper的ACL权限功能,用更为程序员熟悉rw风格取代,简化了Zookeeper的临时节点和序列节点等类型,取代为在创建节点时是否指定保持心跳,心跳断掉时节点会自动删除。Fourinone是高可用的,没有单点问题,可以有任意多个复本,它的复制不是定时而是基于内容变更复制,有更高的性能,Fourinone实现了领导者选举算法(但不是Paxos),在领导者服务器宕机情况下,会自动不延时的将请求切换到备份服务器上,选举出新的领导者进行服务,这个过程中,心跳节点仍然能保持健壮的稳定性,迅速跟新的领导者保持心跳连接。基于Fourinone可以轻松实现分布式配置信息,集群管理,故障节点检测,分布式锁,以及淘宝configserver等等协同功能。   其次, Fourinone可以提供完整的分布式缓存功能。如果对一个中小型的互联网或者企业应用,仅仅利用domain/node进行k/v的存储即可,因为domain/node都是内存操作而且读写锁分离,同时拥有复制备份,完全满足缓存的高性能与可靠性。对于大型互联网应用,高峰访问量上百万的并发读写吞吐量,会超出单台服务器的承受力,Fourinone提供了fa?ade的解决方案去解决大集群的分布式缓存,利用硬件负载均衡路由到一组fa?ade服务器上,fa?ade可以自动为缓存内容生成key,并根据key准确找到散落在背后的缓存集群的具体哪台服务器,当缓存服务器的容量到达限制时,可以自由扩容,不需要成倍扩容,因为fa?ade的算法会登记服务器扩容时间版本,并将key智能的跟这个时间匹配,这样在扩容后还能准确找到之前分配到的服务器。另外,基于Fourinone可以轻松实现web应用的session功能,只需要将生成的key写入客户端cookie即可。   Fourinone对于分布式大数据量并行计算的解决方案不同于复杂的hadoop,它不像hadoop的中间计算结果依赖于hdfs,它使用不同于map/reduce的全新设计模式解决问题。Fourinone有“包工头”,“农民工”,“手工仓库”的几个核心概念。“农民工”为一个计算节点,可以部署在多个机器,它由开发者自由实现,计算时,“农民工”到“手工仓库”获取输入资源,再将计算结果放回“手工仓库”返回给“包工头”。“包工头”负责承包一个复杂项目的一部分,可以理解为一个分配任务和调度程序,它由开发者自己实现,开发者可以自由控制调度过程,比如按照“农民工”的数量将源数据切分成多少份,然后远程分配给“农民工”节点进行计算处理,它处理完的中间结果数据不限制保存在hdfs里,而可以自由控制保存在分布式缓存、数据库、分布式文件里。如果需要结果数据的合并,可以新建立一个“包工头”的任务分配进行完成。多个“包工头”之间进行责任链式处理。总的来说,是将大数据的复杂分布式计算,设计为一个链式的多“包工头”环节去处理,每个环节包括利用多台“农民工”机器进行并行计算,无论是拆分计算任务还是合并结果,都可以设计为一个单独的“包工头”环节。这样做的好处是,开发者有更大能力去深入控制并行计算的过程,去保持使用并行计算实现业务逻辑的完整性,而且对各种不同类型的并行计算场景也能灵活处理,不会因为某些特殊场景被map/reduce的框架限制住思维,并且链式的每个环节也方便进行监控过程。   Fourinone也可以当成简单的mq来使用,将domain视为mq队列,每个node为一个队列消息,监控domain的变化事件来获取队列消息。也可以将domain视为订阅主题,将每个订阅者注册到domain的node上,发布者将消息逐一更新每个node,订阅者监控每个属于自己的node的变化事件获取订阅消息,收到后删除内容等待下一个消息。但是Fourinone不实现JMS的规范,不提供JMS的消息确认和消息过滤等特殊功能,不过开发者可以基于Fourinone自己去扩充这些功能,包括mq集群,利用一个独立的domain/node建立队列或者主题的key隐射,再仿照上面分布式缓存的智能根据key定位服务器的做法实现集群管理。   Fourinone整体代码短小精悍,跟Hadoop, Zookeeper, Memcache, ActiveMq等开源产品代码上没有任何相似性,不需要任何依赖,引用一个jar包就可以嵌入式使用,良好支持window环境,可以在一台机器上模拟分布式环境,更方便开发。   开发包里自带了一系列傻瓜上手demo,包括分布式计算、统一配置管理、集群管理、分布式锁、分布式缓存、MQ等方面, 每个demo均控制在少许行代码内,但是涵盖了Fourinone主要的功能,方便大家快速理解并掌握。   Fourinone 2.0新增功能:   Fourinone2.0提供了一个4合1分布式框架和简单易用的编程api,实现对多台计算机cpu,内存,硬盘的统一利用,从而获取到强大计算能力去解决复杂问题。Fourinone框架提供了一系列并行计算模式(农民工/包工头/职介绍/手工仓库)用于利用多机多核cpu的计算能力;提供完整的分布式缓存和小型缓存用于利用多机内存能力;提供像操作本地文件一样操作远程文件(访问,并行读写,拆分,排它,复制,解析,事务等)用于利用多机硬盘存储能力;由于多计算机物理上独立,Fourinone框架也提供完整的分布式协同和锁以及简化MQ功能,用于实现多机的协作和通讯。   一、提供了对分布式文件的便利操作, 将集群中所有机器的硬盘资源利用起来,通过统一的fttp文件路径访问,如:   windows:fttp://v020138.sqa.cm4/d:/data/a.log   linux:fttp://v020138.sqa.cm4/home/user/a.log   比如以这样的方式读取远程文件:   FttpAdapter fa = FttpAdapter("fttp://v020138.sqa.cm4/home/log/a.log");   fa.getFttpReader().readAll();   提供对集群文件的操作支持,包括:   1、元数据访问,添加删除,按块拆分, 高性能并行读写,排他读写(按文件部分内容锁定),随机读写,集群复制等   2、对集群文件的解析支持(包括按行,按分割符,按最后标识读取)   3、对整形数据的高性能读写支持(ArrayInt比ArrayList存的更多更快)   4、两阶段提交和事务补偿处理   5、自带一个集群文件浏览器,可以查看集群所有硬盘上的文件(不同于hadoop的namenode,没有单点问题和容量限制)   总的来说, 将集群看做一个操作系统,像操作本地文件一样操作远程文件。   但是fourinone并不提供一个分布式存储系统,比如文件数据的导入导出、拆分存储、负载均衡,备份容灾等存储功能,不过开发人员可以利用这些api去设计和实现这些功能,用来满足自己的特定需求。   二、自动化class和jar包部署   class和jar包只需放在工头机器上, 各工人机器会自动获取并执行,兼容操作系统,不需要进行安全密钥复杂配置   三、网络波动状况下的策略处理,设置抢救期,抢救期内网络稳定下来不判定结点死亡   本软件由www.gg265.net www.fmrpg.com站长提供
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值