【IT168 技术开发】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,它拥有以下众多方便的静态工厂方法:
来看一个具体的例子:
代码清单: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
可见,这六个任务共享了线程池中的三个线程。由于ExecutorService用线程池中三个线程服务于提交的任务,避免了为每一个任务创建独立线程的代价,具有更高的运行性能。
| 第1页: 了解JDK 5.0的Executor | 第2页: Spring对Executor所提供的抽象 |