执行器
在前面的所有示例中,由新的线程(由其Runnable对象定义)和线程本身(由Thread对象定义)完成的任务之间存在紧密的联系,这适用于小型应用程序,但在大型应用程序中,将线程管理和创建与应用程序的其余部分分开是有意义的,封装这些函数的对象称为执行器,以下小节详细描述了执行器。
执行器接口定义三个执行器对象类型。
线程池是最常见的执行器实现类型。
Fork/Join是一个利用多个处理器的框架(JDK 7中的新增功能)。
执行器接口java.util.concurrent包定义了三个执行器接口:
Executor,一个支持启动新任务的简单接口。
ExecutorService,Executor的子接口,它添加了有助于管理生命周期的功能,包括单个任务和执行器本身。
ScheduledExecutorService,ExecutorService的子接口,支持将来和/或定期执行任务。
通常,引用执行器对象的变量被声明为这三种接口类型之一,而不是执行器类类型。
Executor接口Executor接口提供单个方法execute,旨在成为常见线程创建语法的替代方法,如果r是Runnable对象,并且e是Executor对象,则可以替换
(new Thread(r)).start();
为
e.execute(r);
但是,execute的定义不太具体,低级别语法创建一个新线程并立即启动它,根据Executor实现,execute可能会做同样的事情,但更有可能使用现有的工作线程来运行r,或者将r放在队列中以等待工作线程变为可用(我们将在线程池的部分中描述工作线程)。
java.util.concurrent中的执行器实现旨在充分利用更高级的ExecutorService和ScheduledExecutorService接口,尽管它们也可以与基本Executor接口一起使用。
ExecutorService接口ExecutorService接口使用类似但更通用的submit方法补充execute,与execute一样,submit接受Runnable对象,但也接受Callable对象,这允许任务返回一个值。submit方法返回一个Future对象,该对象用于检索Callable返回值并管理Callable和Runnable任务的状态。
ExecutorService还提供了提交大量Callable对象的方法,最后,ExecutorService提供了许多用于管理执行器关闭的方法,为了支持立即关闭,任务应该正确处理中断。
ScheduledExecutorService接口ScheduledExecutorService接口使用schedule补充其父级ExecutorService的方法,在指定的延迟后执行Runnable或Callable任务,此外,接口定义了scheduleAtFixedRate和scheduleWithFixedDelay,它们以定义的间隔重复执行指定的任务。
线程池java.util.concurrent中的大多数执行器实现都使用由工作线程组成的线程池,这种线程与它执行的Runnable和Callable任务分开存在,通常用于执行多个任务。
使用工作线程可以最小化由于创建线程而带来的开销,线程对象使用大量内存,在大型应用程序中,分配和释放许多线程对象会产生大量的内存管理开销。
一种常见类型的线程池是固定线程池,这种类型的池始终具有指定数量的线程,如果一个线程在它仍在使用时以某种方式被终止,它将自动被一个新线程替换,任务通过内部队列提交到池中,当活动任务多于线程时,该队列将保存额外的任务。
固定线程池的一个重要优点是使用它的应用程序可以优雅地降级,要理解这一点,请考虑一个Web服务器应用程序,其中每个HTTP请求都由一个多带带的线程处理。如果应用程序只是为每个新的HTTP请求创建一个新线程,并且系统接收的请求数量超过了可以立即处理的数量,当所有这些线程的开销超过系统容量时,应用程序将突然停止响应所有请求。由于可以创建的线程数量有限制,应用程序不会像HTTP请求进入时那样快地为它们提供服务,而是以系统能够承受的最快速度为它们提供服务。
创建使用固定线程池的执行器的一种简单方法是在java.util.concurrent.Executors中调用newFixedThreadPool工厂方法,该类还提供以下工厂方法:
newCachedThreadPool方法使用可扩展线程池创建执行器,此执行器适用于启动许多短期任务的应用程序。
newSingleThreadExecutor方法创建一次执行单个任务的执行器。
有几个工厂方法是上述执行器的ScheduledExecutorService版本。
如果上述工厂方法提供的执行器均无法满足你的需求,构造java.util.concurrent.ThreadPoolExecutor或java.util.concurrent.ScheduledThreadPoolExecutor的实例将为你提供额外选项。
Fork/Joinfork/join框架是ExecutorService接口的一个实现,可帮助你利用多个处理器,它专为可以递归分解成小块的工作而设计,目标是使用所有可用的处理能力来增强应用程序的性能。
与任何ExecutorService实现一样,fork/join框架将任务分配给线程池中的工作线程,fork/join框架是不同的,因为它使用了工作窃取算法,没有事情可做的工作线程可以从仍然忙碌的其他线程中窃取任务。
fork/join框架的中心是ForkJoinPool类,它是AbstractExecutorService类的扩展,ForkJoinPool实现了核心工作窃取算法,可以执行ForkJoinTask进程。
基础用法使用fork/join框架的第一步是编写执行工作片段的代码,你的代码应类似于以下伪代码:
if (我的工作部分足够小) 直接做这项工作 else 把我的工作分成两块 调用这两块并等待结果
将此代码包装在ForkJoinTask子类中,通常使用其更专业的类型之一,RecursiveTask(可以返回结果)或RecursiveAction。
在ForkJoinTask子类准备就绪后,创建表示要完成的所有工作的对象,并将其传递给ForkJoinPool实例的invoke()方法。
模糊清晰度为了帮助你了解fork/join框架的工作原理,请考虑以下示例,假设你想模糊图像,原始源图像由整数数组表示,其中每个整数包含单个像素的颜色值,模糊的目标图像也由与源相同大小的整数数组表示。
通过一次一个像素地处理源数组来完成模糊,将每个像素与其周围像素进行平均(对红色、绿色和蓝色组件进行平均),并将结果放置在目标数组中,由于图像是大型数组,因此此过程可能需要很长时间,通过使用fork/join框架实现的算法,你可以利用多处理器系统上的并发处理,这是一个可能的实现:
public class ForkBlur extends RecursiveAction { private int[] mSource; private int mStart; private int mLength; private int[] mDestination; // Processing window size; should be odd. private int mBlurWidth = 15; public ForkBlur(int[] src, int start, int length, int[] dst) { mSource = src; mStart = start; mLength = length; mDestination = dst; } protected void computeDirectly() { int sidePixels = (mBlurWidth - 1) / 2; for (int index = mStart; index < mStart + mLength; index++) { // Calculate average. float rt = 0, gt = 0, bt = 0; for (int mi = -sidePixels; mi <= sidePixels; mi++) { int mindex = Math.min(Math.max(mi + index, 0), mSource.length - 1); int pixel = mSource[mindex]; rt += (float)((pixel & 0x00ff0000) >> 16) / mBlurWidth; gt += (float)((pixel & 0x0000ff00) >> 8) / mBlurWidth; bt += (float)((pixel & 0x000000ff) >> 0) / mBlurWidth; } // Reassemble destination pixel. int dpixel = (0xff000000 ) | (((int)rt) << 16) | (((int)gt) << 8) | (((int)bt) << 0); mDestination[index] = dpixel; } } ...
现在,你实现抽象的compute()方法,该方法可以直接执行模糊或将其拆分为两个较小的任务,简单的数组长度阈值有助于确定是执行还是拆分工作。
protected static int sThreshold = 100000; protected void compute() { if (mLength < sThreshold) { computeDirectly(); return; } int split = mLength / 2; invokeAll(new ForkBlur(mSource, mStart, split, mDestination), new ForkBlur(mSource, mStart + split, mLength - split, mDestination)); }
如果以前的方法在RecursiveAction类的子类中,那么将任务设置为在ForkJoinPool中运行是很简单的,涉及以下步骤:
创建一个代表要完成的所有工作的任务。
// source image pixels are in src // destination image pixels are in dst ForkBlur fb = new ForkBlur(src, 0, src.length, dst);
创建将运行任务的ForkJoinPool。
ForkJoinPool pool = new ForkJoinPool();
运行任务。
pool.invoke(fb);
有关完整源代码(包括创建目标图像文件的一些额外代码),请参阅ForkBlur示例。
标准实现除了使用fork/join框架来实现在多处理器系统上同时执行任务的自定义算法(例如ForkBlur.java示例),Java SE中已经使用fork/join框架实现了一些通常有用的功能,在Java SE 8中引入的一种这样的实现被java.util.Arrays类用于其parallelSort()方法,这些方法类似于sort(),但通过fork/join框架利用并发性。在多处理器系统上运行时,大型数组的并行排序比顺序排序更快,但是,这些方法如何利用fork/join框架超出了Java教程的范围,有关此信息,请参阅Java API文档。
fork/join框架的另一个实现由java.util.streams包中的方法使用,这是Project Lambda计划用于Java SE 8版本的一部分,有关更多信息,请参阅Lambda表达式部分。
上一篇:Lock对象 下一篇:原子变量文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/73062.html
摘要:在这个示例中我们使用了一个单线程线程池的。在延迟消逝后,任务将会并发执行。这是并发系列教程的第一部分。第一部分线程和执行器第二部分同步和锁第三部分原子操作和 Java 8 并发教程:线程和执行器 原文:Java 8 Concurrency Tutorial: Threads and Executors 译者:BlankKelly 来源:Java8并发教程:Threads和Execut...
Java™ 教程 Java教程是为JDK 8编写的,本页面中描述的示例和实践没有利用在后续版本中引入的改进。 Java教程是希望使用Java编程语言创建应用程序的程序员的实用指南,其中包括数百个完整的工作示例和数十个课程,相关课程组被组织成教程。 覆盖基础知识的路径 这些教程以书籍的形式提供,如Java教程,第六版,前往Amazon.com购买。 入门 介绍Java技术和安装Java开发软件并使用...
并发 计算机用户想当然地认为他们的系统一次可以做不止一件事,他们设想他们可以继续在文字处理器中工作,而其他应用程序则下载文件、管理打印队列和流音频,即使是单个应用程序通常也希望一次完成多个任务。例如,流式音频应用程序必须同时从网络上读取数字音频、解压缩、管理回放并更新其显示,甚至文字处理器应始终准备好响应键盘和鼠标事件,无论重新格式化文本或更新显示有多繁忙,可以执行此类操作的软件称为并发软件。 J...
摘要:并发教程原子变量和原文译者飞龙协议欢迎阅读我的多线程编程系列教程的第三部分。如果你能够在多线程中同时且安全地执行某个操作,而不需要关键字或上一章中的锁,那么这个操作就是原子的。当多线程的更新比读取更频繁时,这个类通常比原子数值类性能更好。 Java 8 并发教程:原子变量和 ConcurrentMap 原文:Java 8 Concurrency Tutorial: Synchroni...
高级并发对象 到目前为止,本课程重点关注从一开始就是Java平台一部分的低级别API,这些API适用于非常基础的任务,但更高级的任务需要更高级别的构建块,对于充分利用当今多处理器和多核系统的大规模并发应用程序尤其如此。 在本节中,我们将介绍Java平台5.0版中引入的一些高级并发功能,大多数这些功能都在新的java.util.concurrent包中实现,Java集合框架中还有新的并发数据结构。 ...
摘要:未来的主要发布基于。在中调用函数支持从代码中直接调用定义在脚本文件中的函数。下面的函数稍后会在端调用为了调用函数,你首先需要将脚本引擎转换为。调用函数将结果输出到,所以我们会首先看到输出。幸运的是,有一套补救措施。 原文:Java 8 Nashorn Tutorial 译者:飞龙 协议:CC BY-NC-SA 4.0 这个教程中,你会通过简单易懂的代码示例,来了解Nashorn Ja...
阅读 2716·2021-11-19 09:40
阅读 5252·2021-09-27 14:10
阅读 2075·2021-09-04 16:45
阅读 1351·2021-07-25 21:37
阅读 2979·2019-08-30 10:57
阅读 2945·2019-08-28 17:59
阅读 1037·2019-08-26 13:46
阅读 1391·2019-08-26 13:27