*1: 定义了几个任务
*2: 初始了任务执行工具。任务的执行框架将会在后面解释。
*3: 执行任务,任务启动时返回了一个 Future 对象,如果想得到任务执行的结果或者是异常可对这个 Future 对象进行操作。 Future 所含的值必须跟 Callable 所含的值对映,比如说例子中 Future 对印 Callable
*4: 任务 1 正常执行完毕, future1.get() 会返回线程的值
*5: 任务 2 在进行一个死循环,调用 future2.cancel(true) 来中止此线程。传入的参数标明是否可打断线程, true 表明可以打断。
*6: 任务 3 抛出异常,调用 future3.get() 时会引起异常的抛出。
运行 Executor 会有以下运行结果:
looping.... Task done. //*1 looping.... looping....//*2 looping.... looping.... looping.... looping.... Thread 2 terminated? :true //*3 //*4 java.util.concurrent.ExecutionException: java.lang.Exception: Callable terminated with Exception! at java.util.concurrent.FutureTask$Sync.innerGet(FutureTask.java:205) at java.util.concurrent.FutureTask.get(FutureTask.java:80) at concurrent.Executor.main(Executor.java:43) ……. |
*1: 任务 1 正常结束
*2: 任务 2 是个死循环,这是它的打印结果
*3: 指示任务 2 被取消
*4: 在执行 future3.get() 时得到任务 3 抛出的异常
3:新的任务执行架构
在 Java 5.0 之前启动一个任务是通过调用 Thread 类的 start() 方法来实现的,任务的提于交和执行是同时进行的,如果你想对任务的执行进行调度或是控制同时执行的线程数量就需要额外编写代码来完成。 5.0 里提供了一个新的任务执行架构使你可以轻松地调度和控制任务的执行,并且可以建立一个类似数据库连接池的线程池来执行任务。这个架构主要有三个接口和其相应的具体类组成。这三个接口是 Executor, ExecutorService 和 ScheduledExecutorService ,让我们先用一个图来显示它们的关系:
图的左侧是接口,图的右侧是这些接口的具体类。注意 Executor 是没有直接具体实现的。
Executor 接口:
是用来执行 Runnable 任务的,它只定义一个方法:
- execute(Runnable command) :执行 Ruannable 类型的任务
ExecutorService 接口:
ExecutorService 继承了 Executor 的方法,并提供了执行 Callable 任务和中止任务执行的服务,其定义的方法主要有:
- submit(task) :可用来提交 Callable 或 Runnable 任务,并返回代表此任务的 Future 对象
- invokeAll(collection of tasks) :批处理任务集合,并返回一个代表这些任务的 Future 对象集合
- shutdown() :在完成已提交的任务后关闭服务,不再接受新任务
- shutdownNow() :停止所有正在执行的任务并关闭服务。
- isTerminated() :测试是否所有任务都执行完毕了。
- isShutdown() :测试是否该 ExecutorService 已被关闭
ScheduledExecutorService 接口
在 ExecutorService 的基础上, ScheduledExecutorService 提供了按时间安排执行任务的功能,它提供的方法主要有:
- schedule(task, initDelay): 安排所提交的 Callable 或 Runnable 任务在 initDelay 指定的时间后执行。
- scheduleAtFixedRate() :安排所提交的 Runnable 任务按指定的间隔重复执行
- scheduleWithFixedDelay() :安排所提交的 Runnable 任务在每次执行完后,等待 delay 所指定的时间后重复执行。
代码: ScheduleExecutorService 的例子
public class ScheduledExecutorServiceTest { public static void main(String[] args) throws InterruptedException, ExecutionException{ //*1 ScheduledExecutorService service = Executors.newScheduledThreadPool(2); //*2 Runnable task1 = new Runnable() { public void run() { System.out.println("Task repeating."); } }; //*3 final ScheduledFuture future1 = service.scheduleAtFixedRate(task1, 0, 1, TimeUnit.SECONDS); //*4 ScheduledFuture future2 = service.schedule(new Callable(){ public String call(){ future1.cancel(true); return "task cancelled!"; } }, 5, TimeUnit.SECONDS); System.out.println(future2.get()); //*5 service.shutdown(); } } |
这个例子有两个任务,第一个任务每隔一秒打印一句“ Task repeating ” , 第二个任务在 5 秒钟后取消第一个任务。
*1: 初始化一个 ScheduledExecutorService 对象,这个对象的线程池大小为 2 。
*2: 用内函数的方式定义了一个 Runnable 任务。
*3: 调用所定义的 ScheduledExecutorService 对象来执行任务,任务每秒执行一次。能重复执行的任务一定是 Runnable 类型。注意我们可以用 TimeUnit 来制定时间单位,这也是 Java 5.0 里新的特征, 5.0 以前的记时单位是微秒,现在可精确到奈秒。
*4: 调用 ScheduledExecutorService 对象来执行第二个任务,第二个任务所作的就是在 5 秒钟后取消第一个任务。
*5: 关闭服务。
Executors 类
虽然以上提到的接口有其实现的具体类,但为了方便 Java 5.0 建议使用 Executors 的工具类来得到 Executor 接口的具体对象,需要注意的是 Executors 是一个类,不是 Executor 的复数形式。 Executors 提供了以下一些 static 的方法:
- callable(Runnable task): 将 Runnable 的任务转化成 Callable 的任务
- newSingleThreadExecutor: 产生一个 ExecutorService 对象,这个对象只有一个线程可用来执行任务,若任务多于一个,任务将按先后顺序执行。
- newCachedThreadPool(): 产生一个 ExecutorService 对象,这个对象带有一个线程池,线程池的大小会根据需要调整,线程执行完任务后返回线程池,供执行下一次任务使用。
- newFixedThreadPool(int poolSize) :产生一个 ExecutorService 对象,这个对象带有一个大小为 poolSize 的线程池,若任务数量大于 poolSize ,任务会被放在一个 queue 里顺序执行。
- newSingleThreadScheduledExecutor :产生一个 ScheduledExecutorService 对象,这个对象的线程池大小为 1 ,若任务多于一个,任务将按先后顺序执行。
- newScheduledThreadPool(int poolSize): 产生一个 ScheduledExecutorService 对象,这个对象的线程池大小为 poolSize ,若任务数量大于 poolSize ,任务会在一个 queue 里等待执行
以下是得到和使用 ExecutorService 的例子:
代码:如何调用 Executors 来获得各种服务对象
//Single Threaded ExecutorService ExecutorService singleThreadeService = Executors.newSingleThreadExecutor(); //Cached ExecutorService ExecutorService cachedService = Executors.newCachedThreadPool(); //Fixed number of ExecutorService ExecutorService fixedService = Executors.newFixedThreadPool(3); //Single ScheduledExecutorService ScheduledExecutorService singleScheduledService = Executors.newSingleThreadScheduledExecutor(); //Fixed number of ScheduledExecutorService ScheduledExecutorService fixedScheduledService = Executors.newScheduledThreadPool(3); |
4: Lockers和 Condition接口
在多线程编程里面一个重要的概念是锁定,如果一个资源是多个线程共享的,为了保证数据的完整性,在进行事务性操作时需要将共享资源锁定,这样可以保证在做事务性操作时只有一个线程能对资源进行操作,从而保证数据的完整性。在 5.0 以前,锁定的功能是由 Synchronized 关键字来实现的,这样做存在几个问题:
- 每次只能对一个对象进行锁定。若需要锁定多个对象,编程就比较麻烦,一不小心就会出现死锁现象。
- 如果线程因拿不到锁定而进入等待状况,是没有办法将其打断的
在 Java 5.0 里出现两种锁的工具可供使用,下图是这两个工具的接口及其实现: