JVM线程池发展趋势
多线程已经成为大多数开发者的兴趣所在了。他们努力尝试想找出最优策略来解决这个问题。过去已经有各种尝试去标准化这些方案。特别是随着大数据,实 时分析等问题领域的兴起,又存在着新的挑战。在这个方向需要走的一步是“Doug Lea”的作品(一部巨作),以并发框架(JSR 166)的形式提供给我们。
现在开始区分并发和并行性。这些只是不同的策略,而且市面上有很多框架提供,都能帮我们达到相同的目的。但在选择的时候如果能同时知道他们内部的实 现细节对我们也是大有好处的。本文将要探究JVM中线程池和线程共享的一些稳定有效的选项。当然,随着多核处理器的广泛使用,新的问题也随之而来。开发人 员也开始思考利用高级硬件的“mechanical sympathy”(译者注:表示底层硬件的运作方式以及与硬件运行方式协同的软件编程)来提高性能。
个人以为,当讨论线程池时,目前广泛应用的主要有下述机制:
- Executor框架提供的线程。
- LMAX的Ring Buffer概念 (译者注:Ring Buffer即环形缓冲,LMAX是一种新型零售金融交易平台,其框架能以低延迟产生大量交易,LMAX建立在JVM平台上。
- 基于Actor(事件)的实现。
并发框架下的线程池选项:
首先,我个人不赞同使用当下流行的线程池概念,而应该使用工作队列的概念。简而言之,在一个执行框架可供选择的各种不同选项都是基于某种顺序数据结 构,如数组或队列(阻塞或非阻塞)之类的,比如ConcurrentLinkedQueue(并发链式队列),ArrayBlockingQueue(数 组阻塞队列), LinkedBlockingQueue(链式阻塞队列)等等。文档表明,尽管它们的使用环境各不相同,但他们隐含的本质/数据结构有相同的性质,如顺序 插入和遍历。
优势:
- 减少线程创建导致的延迟
- 通过优化线程数量,可以解决资源不足的问题。
这些可以使应用程序和服务器应用响应更快。使用线程池看似一个很不错的解决方案但是却有一个根本性的缺陷:连续争用问题。这里是Java中关于一些并发框架下线程池选项的讨论。
Disruptor(环形缓冲):
(LMAX的一个基于环形缓冲区的高性能进程间消息库)LMAX的开发人员使用一个干扰框架来解决连续争用问题,这个框架是基于一个叫环形缓冲的数 据结构。它可能是线程间发送消息的最有效方式了。它是队列的一种替代实现方式,但它又和SEDA和Actors(译者注:这两种都是和Disruptor 类似的并发模型)有一些共同特征。向Disruptor中放入消息需要两步,第一步申请一个环形缓冲的槽位,槽位可为用户提供写对应数据的记录。然后需要 提交该条记录,为了能灵活使用内存,2步法是必须的。只有经过提交,这条消息才能对消费者线程可见。下图描述了环状缓冲这个数据结构(Disruptor 的核心):
(译者注:LMAX的核心是一个业务逻辑处理器,而该业务逻辑处理器的核心是Disruptor,这是一个并发组件,能够在无锁的情况下实现网络的Queue并发操作)
Disruptor在多核平台上能达到很低的延迟同时又有高吞吐量,尽管线程程间需要共享数据以及传递消息。
它的独特之处在于锁和免争用结构。它甚至不使用CAS或内存保护。若想了解关于它的更多细节,这里有一篇不错的文章和官网。使用Disruptor的一个缺点(事实上也算不上缺点)是,你需要提前告知Disruptor应用程序完成任务所需要的线程数。
基于事件:
对于传统线程池机制,一个强大的替代方案就是基于事件模型。这种基于事件的线程轮询/线程池/线程调度机制在函数式编程中很常见。关于这个概念的一个非常流行的实现是基于actor的系统(译者注:Scala的并发系统),Akka已成为其实际上的标准。(译者注:Akka,一种善于处理进程间通信的框架)
Actors是非常轻量级的并发实体。它们使用一种事件驱动的接收循环来异步处理消息。消息模式匹配可以很方便地表述一个actor的行为。它们提 高了抽象级别从而使写,测试,理解和维护并发/分布式系统更加容易。让你专注于工作流——消息如何流入系统——而不是低层次的基本概念如线程,锁以及套接 字IO。一个线程可以分配多个或单个actor,而且两种模型都是依需要选择的。
像Akka这种基于actor的系统的优势有如下所列:
- 可封装
- 可监督
- 可配置执行
- 位置透明
- 重试机制
注:调试一个基于actor的系统是一个非常艰难的事情。
Disruptor使用一个线程一个消费者模式,不同于Actors使用N个线程M个消费者模式。比如,你可以拥有任意多的actors,然后它们 会被分散到一些数目固定的线程中(通常是一个核一个线程),至于其他的部分,actor模型就和Disruptor模型差不多了;特别是用于批处理的时 候。
我最初在因特网上搜多到的答案也说明开源空间中关于确定JVM选项基准的贡献还是有一些的。其中一个选项是ExecutorBenchmarkt。它是一个并行任务的开源测试框架。它是用Scala编写的,但是可以用于Java和Scala负载。
简而言之,快速发展的软硬件行业在呈现新挑战给我们的同时也提供了大量解决应用程序容错性和响应性的方法。对于不可预知的少量线程,我建议使用 JDK并发框架中的线程池机制。对于大量规模相似的任务,个人建议使用Disruptor。Disruptor的确是有一点学习曲线,但在性能和扩展性方 面的收获远远值得投入的学习时间。在应用程序需要某种重试或管理机制,以及分布式任务时,建议使用Akka的Actor模型。尽管结果还有可能被其它因素 所影响,你还是会选择map reduce或fork/join模型或是其它自定义实现一个分布式应用程序。
原文链接: dzone 翻译: ImportNew.com - 范 忠瑞译文链接: http://www.importnew.com/15082.html