java平台一直对并发程序设计和多线程有这很好的支持。但在早期这种支持也只是在应用层调用原生结构,这种方式最大的缺陷就是使这些原始构件有效的被调用;否则,应用将不能正确运行或者不能达到期望结果。

Executor框 架自java1.5中被作为comcurrency包的一部分被引入。它是java多线程的一个抽象层实现,并且作为java中首个实用的并发框架被用来 标准调用、在并行线程中调度、执行以及控制异步任务。执行规则在创建构造器的时候就已经被定义,之后executor按照之前设置好的规则来调用并发线 程。

在java中Executor的实现使用线程池,该线程池由workder线程组成。由于worker线程的管理都由该框架来完成,因此和早先多线程方法来比内存开销小了很多。

图1:java Executor Service

在java1.5之前,多线程应用使用线程组、线程池或者自定义线程池,这样一来,整个线程管理需要程序员牢记以下几点责任:

  • 线程同步

  • 线程等待

  • 线程join

  • 线程锁

  • 线程通知

  • 处理死锁

以 及更多在应用需求之外产生的并发执行问题。在实际项目实现过程中有时控制多线程应用是非常困难的。同时线程的表现行为也依赖与所在应用的部署和运行环境, 因此,同一个应用在不同的部署环境下可能有不能的行为结果。例如,处理器速度、RAM内存大小、带宽都会直接影响多线程应用。所以我们在构建多线程架构之 前都要谨记这些影响因素。

Java Executor框 架为多线程应用提供了一个更加容易的抽象层。其中executor抽象层隐藏了并发执行部分,程序员只需要关注业务逻辑实现即可。在executor框架 中所有的并行工作都被视为任务而不是看作简单的线程,所以此时应用只需要简单的处理Runnable实例(这是一组基本任务或并行工作的集合),接下来这 些任务会被传给一个Executor来处理。ExecutorService接口继承了Executor接口。此外java Executor框架有生命周期方法来管理真个并发执行流程。

Executor通 过使用Runnable或这Callable实例来创建任务。使用Runnable时,run方法并不返回任何值或者抛出异常;但是Callable在这 方面提供了更多功能,它定义了一个call方法来允许返回计算值并且可在future处理中使用这个值,同时如果需要的话还可以抛出异常。

而 FutureTask类又是另外一个重要的组件,该组件可以用来获取处理后的信息。该类的实例可以用来包装Callable或者Runnable。我们可 以通过ExecutorService的submit方法的一个该类实例的返回值,同时我们还可以在调用execute方法前手动使用 FutureTask来包装我们的任务。

以下是实现java ThreadPoolExecutor的功能步骤:

  • 创建线程池

  • 创建一个队列来存放所有还没有被分配给线程池中线程的任务

  • 当队列中一个或多个任务不能被分配时用拒绝处理程序(Rejection handler)来处理。按默认拒绝策略,拒绝处理程序将简单的抛出一个RejectedExecutionException运行时异常,应用程序可以捕获或丢弃该异常

图2:Thread Pool Executor

下面的例子会展示executor框架的生命周期。这个例子仅覆盖基本的步骤和接口,还有更多的属性和子类来满足不同类型应用的需求。

下面的类实现了Runnable接口,它的实例将在下一小节的代码中被用作任务。

代码片段1:实现Runnable接口的类

代码片段2:实现Executor任务的类

接下来我们将描述在应用中实现java executor框架的步骤。

创建Executor

首先,我们需要创建一个Executor或者ExecutorService实例。Executor类有很多静态工厂方法来根据应用的不同需求创建ExecutorService。以下是两种创建executor服务的两种主要方式:

  • newFixedThreadPool()返回一个包含已初始化并且没有上限队列以及固定数量线程的ThreadPoolExecutor实例

  • newCachedThreadPool()返回一个包含无上限队列和无线程限制的ThreadPoolExecutor实例

第 一种方式中执行过程中并未创建额外的线程。因此如果没有空闲线程可用那么任务将等待直到有空闲线程才能执行。在第二种方式中,已经创建的线程如果空闲则将 被再利用,但是如果没有线程可用时将创建一个新的线程并将该线程加入到线程池中用来完成任务处理。对于空闲时间超过一个设定的超时时间以后,将被自动从线 程池中移除。

下面是创建10个固定线程的线程池:

下面是创建缓冲线程池代码:

下面则是自定义线程池的例子。其中参数值由项目具体需求决定。这里线程池有8个核心线程可以并行处理,最大线程数为12。队列可以持有250个任务。这里有一点需要注意的是池的大小需要保持较大的值以满足所有任务。空闲时间限制保持在5毫秒。

创建一个或多个任务放入队列

创建executor完成之后紧接着就要创建任务了。创建一个或多个可以做为Runnable或Callable实例执行的任务。在这个框架中,所有的任务都是在队列中被创建和填充。当任务创建完成以后填充队列被提交并发执行。

提交任务到Executor

创 建完ExecutorService和task之后,我们需要通过submit或者execute方法提交任务到executor。现在按照我们的配置, 任务将被从队列中取出并发执行。例如,如果配置并发执行数为5,那么一次将从队列中取出5个任务来并发执行。该处理l直到队列中所有的任务都处理完才停 止。

执行任务

接 下来,实际的任务执行将由框架来管理。Executor负责对任务的执行、线程池、同步以及队列进行管理。如果线程池中线程数少于配置中的最小线程 数,executor将按照要求创建新的线程来处理队列中的任务直到达到最小线程数为止;如果线程数大于配置的最小线程数,那么线程池将不会再创建线程。 相反,任务将一直保持在队列中,直到有新的线程空闲来处理该请求。如果队列已满,则会创建一个新的处理线程,但换句话说这取决于创建executor使所 使用用的构造函数。

关闭Executor

终止executor的方式是调用shutdown方法。同时我们可以选择平滑关闭或者直接关闭。

总结

经过以上的讨论,很明显并发框架是非常高效的并且在多线程应用中被广泛使用。这是java中引入的第一个并发框架,它为并发编程增加了一个新的维度。程序员通过使用executor框架增加了程序的灵活性,同时也避免了内存同步的开销而使程序更加稳定。

你可能感兴趣的内容
Java序列化与static 收藏,3230 浏览
0条评论

dexcoder

这家伙太懒了 <( ̄ ﹌  ̄)>
Owner