技术开发 频道

Spring对JDK 5.0执行器的支持



    概述
    JDK 5.0新增了一个并发工具包java.util.concurrent,该工具包是由Doug Lea设计并作为JSR 166添加到JDK 5.0中。这是一个非常流行的并发工具包。他提供了功能强大的、高层次的线程构造器,包含了执行器、线程任务框架、线程安全队列、计时器、锁(包含了原子级别的锁)和其他一些同步的基本类型。

    执行器Executor是并发工具包中一个重要的类,它对Runnable实例的执行进行了抽象,实现者可以提供具体的实现:如简单的以一个线程来运行Runnable,或者通过一个线程池为Runnable提供共享线程。

    因为Executor是JDK 5.0新增的类,其实现者大多拥有线程池的内在支持,Spring 2.0 为Executor处理引入了一个新的抽象层,以便将线程池引入到JDK 1.3和1.4环境中,同时屏蔽掉JDK 1.3、1.4、5.0以及Java EE环境中线程池实现的差异。

    了解JDK 5.0的Executor java.util.concurrent.Executor接口的主要目的是要将“任务提交”和“任务执行”两者分离解耦。该接口定义了任务提交的方法,实现者可以提供不同的任务运行机制,解决具体的线程使用规则、调度方式等问题。
Executor只有一个方法:void execute(Runnable command) ,它接受任何实现了Runnable的实例,这个实例代表了一个需要执行的任务。调用者可以使用如下的代码提交任务:
    Executor executor = anExecutor;
    executor.execute(new RunnableTask1());①提交一个任务
    executor.execute(new RunnableTask2());②提交另一个任务
    Executor本身并没有要求实现者以何种方式运行这些任务,一个简单的实现者甚至可以在接受提交任务时,在主线程中运行它们。下面是一个Executor最简单的实现:
public class SimpleExecutor implements Executor { public void execute(Runnable r) { r.run();①在提交时直接运行任务 } }
    但是更多情况下,需要在另外的线程中运行任务,而非在主线程中运行任务,下面是一个稍微有意义一些的实现,它为每一个任务开启一个新的执行线程:
class ThreadPerTaskExecutor implements Executor { public void execute(Runnable r) { new Thread(r).start();①在新的执行线程中执行任务 } }


    但以上这些粗陋的实现并没有多大的实际意义,这也不符合Executor的设计初衷。真正有意义的实现需要引入线程、列队、调度等机制,这样的执行器才更贴近现实的需求。

    Executor接口还有两个子接口:ExecutorService和ScheduledExecutorService。ExecutorService添加了结束任务的管理方法,此外在提交任务时还可以获取一个Future实例,以便通过这个实例跟踪异步任务的运行情况。而ScheduledExecutorService可以对任务进行调度,如指定执行的延迟时间,运行的周期。如ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)方法将安排一个任务在指定一段延迟时间后运行。

    JDK 5.0本身提供的ThreadPoolExecutor类实现了Executor和ExecutorService这两个接口,它使用一个线程池对提交的任务进行调度。对于需要处理数量巨大的短小的并发任务(诸如 Web 服务器、数据库服务器、邮件服务器之类的应用程序需要处理来源远程的大量短小的任务),采用线程池可以带来明显的好处。为每个请求创建一个新线程的开销很大,对于短小的任务线程创建和销毁的时间可能比处理实际任务的时间还要长。除此以外,活动的线程需要消耗资源,过多的线程将导致大量的内存占用。线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用同一线程,线程创建的开销被分摊到了多个任务上。其好处是,由于在任务到达时线程已经存在,所以无意中也消除了任务执行时,因创建线程所带来的延迟。此外,可以通过适当地调整线程池中的参数,让任务的数目超过某个阈值时,强制其它任何新任务阻塞等待,直到获得一个线程来处理为止,从而可以防止资源的无限制占用。

    ThreadPoolExecutor的子类ScheduledThreadPoolExecutor实现了ScheduledExecutorService接口,添加了对任务的调度功能,如指定延迟一小段时间后运行任务,让任务周期性运行。该类明显优于JDK 1.3中的Timer,因为它通过内建的线程池让每个任务在独立的执行线程中运行,而非让所有任务在单一的背景线程中运行。在Timer经常出现的时间漂移,任务挤压等问题基本上得到了规避。 
    java.util.concurrent中为创建这些接口实例提供了一个综合性的工厂类Executors,它拥有以下众多方便的静态工厂方法:
    ●static ExecutorService newFixedThreadPool(int nThreads):创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程;
    ●static ExecutorService newCachedThreadPool():线程池是动态的,不够用时创建新的线程,长时间不用的线程将被回收;
    ●static ScheduledExecutorService newScheduledThreadPool(int corePoolSize, ThreadFactory threadFactory):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
    来看一个具体的例子:
    代码清单 10 ExecutorExample
package com.baobaotao.basic.executor; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class ExecutorExample { private Executor executor; ①声明一个执行器 public void setExecutor(Executor executor) { this.executor = executor; } public void executeTasks() {②用执行器执行多个任务 for (int i = 0; i < 6; i++) { executor.execute(new SimpleTask("task" + i)); } } public static void main(String[] args) { ExecutorExample ee = new ExecutorExample();
③通过工厂类创建一个带3个线程固定线程池的执行器
ee.setExecutor(Executors.newFixedThreadPool(3)); ee.executeTasks(); } } class SimpleTask implements Runnable {④任务类 private String taskName; public SimpleTask(String taskName) { this.taskName = taskName; } public void run() { System.out.println("do "+taskName+"... in Thread:" + Thread.currentThread().getId()); } }
运行以上代码,输入以下信息:
do task0... in Thread:7
do task1... in Thread:8
do task2... in Thread:9
do task3... in Thread:7
do task5... in Thread:9
do task4... in Thread:8
可见,这6个任务共享了线程池中的3个线程。由于ExecutorService用线程池中3个线程服务于提交的任务,避免了为每一个任务创建独立线程的代价,具有更高的运行性能。


    Spring对Executor所提供的抽象
    Spring的org.springframework.core.task.TaskExecutor接口等同于java.util.concurrent.Executor接口。它存在主要意义在于使用线程池时可以将对JDK 5.0的依赖抽象掉,以便在不同版本的JDK中都可以享受线程池执行任务这份饕餮大餐。该接口和JDK 5.0的Executor接口拥相同的execute(Runnable task)方法。TaskExecutor拥有一个SchedulingTaskExecutor子接口,新增了任务调度规则定制的功能。

    在Spring发行包中预定义了一些TaskExecutor实现。它们可以满足大部分应用要求,一般情况下,你不必自行编写实现类。下面,是TaskExecutor的实现类:
    ● SyncTaskExecutor:位于org.springframework.core.task包中,实现了TaskExecutor接口。这个实现不会异步执行任务,相反,每次调用都在发起调用的主线程中执行;
    下面是SchedulingTaskExecutor的实现类:
    ●SimpleAsyncTaskExecutor:位于org.springframework.core.task包中。这个实现没有使用线程池,在每次执行任务时都创建一个新线程。但是,它还是支持对并发总数设限,当超过线程并发总数限制时,阻塞新的任务直到有可用的资源;
    ●ConcurrentTaskExecutor:位于org.springframework.scheduling.concurrent包中。该类是JDK 5.0的Executor的适配器,以便将JDK 5.0的Executor的当成Spring的TaskExecutor使用;
    ●SimpleThreadPoolTaskExecutor:位于org.springframework.scheduling.quartz包中,这个类实际上是继承于Quartz的   SimpleThreadPool类的子类,它将监听Spring的生命周期回调。当你有线程池,需要在Quartz和非Quartz组件中共用时,该类可以发挥它的用处;
     ●ThreadPoolTaskExecutor:位于org.springframework.scheduling.concurrent包中。这个实现类只能在JDK 5.0中使用,它暴露的一些属性,方便在Spring中配置一个java.util.concurrent.ThreadPoolExecutor,并把它包装成TaskExecutor;
    ●TimerTaskExecutor:位于org.springframework.scheduling.timer包中。该类使用一个Timer作为其后台的实现;
    上一小节代码清单 10的ExecutorExample只能在JDK 5.0下运行,如果我们用Spring的TaskExecutor替换①处的Executor,当程序需部署到低版本JDK环境中时,仅需要选择一个合适的实现就可以了。
package com.baobaotao.executor; import org.springframework.core.task.SimpleAsyncTaskExecutor; import org.springframework.core.task.TaskExecutor; public class ExecutorExample { private TaskExecutor executor; ①使用Spring的TaskExecutor替换JDK 5.0的Executor public void setExecutor(TaskExecutor executor) { this.executor = executor; } public void executeTasks() { for (int i = 0; i < 6; i++) { executor.execute(new SimpleTask("task" + i)); } } public static void main(String[] args) { ExecutorExample ee = new ExecutorExample(); ee.setExecutor(new SimpleAsyncTaskExecutor());②使用Spring的TaskExecutor实现类 ee.executeTasks(); } } class SimpleTask implements Runnable { }
    如果我们将ExecutorExample配置成一个Bean,通过注入的方式提供executor属性,就可以方便地选用不同的实现版本。如在JDK 5.0上,你可以选用ThreadPoolTaskExecutor,而在JDK低版本中则可以使用SimpleAsyncTaskExecutor。这样,程序就可以在不同版本的JDK的移植。
    小结
    对于大量并发的短小型任务,使用线程池进行任务的调度可以带来明显的好处:节省资源、增强伸缩性、获得更快的响应速度。JDK 5.0新增加了支持线程池的Executor,Spring考虑到应用程序在不同版本JDK之间的移植问题,通过TaskExecutor对Executor进行了抽象,这样当应用部署到不同版本JDK时,仅需选择适合的实现类就可以了
0
相关文章