第九章 java异步编程--《java多线程编程实战指南-核心篇》
java.util.concurrent.Executor接口是对任务执行进行的抽象,Executor接口使得任务的提交方只需要知道它调用Executor.execute方法便可以使指定任务被执行,无需关系任务执行的细节,使得任务的提交能够与任务执行的具体细节解耦,它在一定程度上能够屏蔽任务同步执行与异步执行的差异,一定程度上缩小了同步编程与异步编程的代码编写方式。
工具类Executors
Executors能够返回默认的线程工厂、将Runnable实例转换为Callable实例、能够返回ExecutorServer实例的快捷方法:
- Executors.newCahedThreadPool()。该方法的返回值相当于:
new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>()),即核心线程池大小为0,最大线程池大小不受限,工作者线程允许的最大空闲时间为60秒,内部以SynchronousQueue为工作队列的一个线程池。这种配置意味着该线程池中的所有工作者线程在空闲了指定的时间后都可以被自动清理掉。由于该线程池的核心线程池大小为0,因此提交给该线程执行的第一个任务会导致该线程池中的第一个工作者线程被创建并启动。后序继续给该线程继续提交任务的时候,由于当前线程池大小已经超过核心线程池大小(0),因此ThreadPoolExecutor此时会将任务缓存到工作队列之中。SynchronousQueue内部并不维护用于存储队列元素的实际存储空间。一个生产者线程在执行SynchronousQueue.offer(E)的时候,如果没有其他消费者线程因执行SynchronousQueue.take()而被暂停,那么SynchronousQueue.off(E)调用会直接返回false,即入队列失败。因此,在该线程池中的所有工作者线程都在执行任务,即无空闲工作者线程的情况下给其提交任务会导致该任务无法被缓存成功。而ThreadPoolExecutor在任务缓存失败且线程池当前大小未达到最大线程池大小(这里的最大线程池大小实际上相当于不限)的情况下会创建并启动新的工作者线程。在极端的情况下,给该线程每提交一个任务都会导致一个新的工作者线程被创建并启动,而这最终会导致系统中的线程过多,从而导致过多的上下文切换而使整个系统被拖慢。因此,Executors.newCahedThreadPool()所返回的线程池适合于用来执行大量耗时短且提交频率较高的任务,而提交频率较高且耗时较长的任务(尤其是包含阻塞操作的任务)则不适合用Executors.newCahedThreadPool()所返回的线程池来执行。
- Executors.newFixedThreadPool(int nThread)。该方法的返回值相当于:
new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()),即一个无界队列为工作队列,核心线程池大小与最大线程池大小均为nThreads且线程池中的空闲工作者线程不会被自动清理的线程池,这是一种线程池大小一旦达到其核心线程池大小就既不会增加也不会减少工作者线程的固定大小的线程池。因此,这样的线程池实例一旦不再需要,我们必须主动将其关闭。
- Executors.newSIngleThreadExecutor。该方法的返回值相当于:
new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),相当于Executors.newFixedThreadPool(1)所返回的线程池。不过,该线程池并非ThreadPoolExecutor实例,而是封装了一个ThreadPoolExecutor实例且对外紧暴露ExecutorService接口锁定义的方法的一个ExecutorService实例。该线程池便于我们实现单(多)生产者-单消费者模式。该线程池确保了在任意一个时刻只有一个任务会被执行,这就形成了类似锁将原本并发的操作改为串行的操作的效果。因此,该线程池适合用来执行访问了非线程安全对象而我们又不希望因此引人锁的任务。该线程池也适合用来执行I/O操作,因为I/O操作往往受限于相当的I/O设备,使用多个线程执行同一种I/O操作可能并不会提高I/O效率,所以如果使用一个线程执行I/O足以满足要求,那么仅使用一个线程即可,这样保障程序的简单性以避免一些不必要的问题。
异步任务的批量执行:CompletionService
java.util.concurrent.CompletionService接口未异步任务的批量提交以及获取这些任务的处理结果提供便利。
ExecutorCompletionService相当于Executor实例与BlockingQueue实例的一个融合体,ExecutorCompletionService每执行完一个异步任务,就将该任务对应的Future实例存入其内部维护的一个BlockingQueue实例之中,而其客户端代码则可以通过ExecutorCompletionService.take()调用来获取这个FUture实例。
异步计算助手:FutureTask
异步任务中采用Runnable实例来表示异步任务,其优点是任务既可以交给一个专门的工作者线程执行,也可以交给一个线程池或者Executor的其他实现类来执行;其缺点是我们无法直接获取任务的执行结果。使用Callable实例来表示异步任务,其优点是我们可以通过ThreadPoolExecutor.submit(Callable<T>)的返回值获取任务的处理结果;其缺点是Callable实例表示的异步任务只能交给线程池执行,而无法直接交给一个专门的工作者线程或者Executor实现类来执行。
java.util.concurrent.FutureTask融合了Runnable和Callable接口的优点,即使Runnable接口的实现类也是Future接口的实现类:
计划任务
java.util.concurrent.ScheduledExecutorService接口定义了一组方法用于执行计划任务:
延迟执行提交任务:
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
周期性地执行提交任务:
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)