9.Java异步编程

一.Java Executor框架

 Runnable接口和Callable接口都是对任务的抽象。java.util.concurrent.Executor接口则是对任务执行的抽象。

 Executor接口功能有限,①只能为客户端代码执行任务,无法将任务的处理结果返回给客户端代码。②Executor接口实现类内部往往会维护一些工作者线程,当不再需要一个Executor实例时,往往需要将该实例内部维护的工作者线程停掉以释放相应的资源,而Executor接口没有定义相应的方法。ExecutorService接口继承自Executor接口,它解决了上述问题。

1-1 实用工具类Executors

 Executors提供了一些能够返回ExecutorService实例的快捷方法,使得可以在不必手动创建ThreadPoolExecutor实例的情况下使用线程池。

表1-1 Executors提供的能够返回ExecutorService实例的快捷方法
方法 适用条件及注意事项
public static ExecutorService newCachedThreadPool()

适用于 ①大量 ②耗时较短 ③提交频繁 的任务。

如果提交的任务耗时较长,可能导致线程池中工作者线程数量无限制地增加,最后导致过多的上下文切换,从而使得整个系统变慢

public static ExecutorService

newCachedThreadPool(ThreadFactory threadFactory)

public static ExecutorService newFixedThreadPool(int nThreads)

核心线程池大小等于最大线程池大小,因此该线程池中的工作者线程永远不会超时,必须在不需要该线程池的时候主动将其关闭。

public static ExecutorService

newFixedThreadPool(int nThreads, ThreadFactory threadFactory)

public static ExecutorService newSingleThreadExecutor()

适用于单(多)生产者——单消费者模式。

该方法的返回值无法被转换为ThreadPoolExecutor类型

public static ExecutorService

newSingleThreadExecutor(ThreadFactory threadFactory)

1.Executors.newCachedThreadPool()

 该方法的返回值相当于:

new ThreadPoolExecutor(
        0, // 核心线程池大小为0
        Integer.MAX_VALUE, // 最大线程池大小不受限
        60L, // 工作者线程最大空闲时间(KeepAliveTime)为60秒
        TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>() // 内部以SynchronousQueue为工作者队列
);
  • 该线程池中的所有工作者线程在空闲了指定的时间后都可以被自动清理掉。
  • 由于该线程池的核心线程池大小为0,因此提交给该线程池执行的第一个任务会导致该线程池中的第一个工作者线程被创建并启动。后续继续给该线程池提交任务的时候,由于当前线程池大小已经超过了核心线程池大小(0),因此ThreadPoolExecutor此时会尝试将任务缓存到工作者队列中(即调用非阻塞的workderQueue.offer方法)。
  • SynchronousQueue内部并不维护用于存储队列元素的实际存储空间。一个线程(生产者线程)在执行SynchronousQueue.offer(E)的时候,如果没有其他线程(消费者线程)因执行SynchronousQueue.take()而被暂停,那么SynchronousQueue.offer(E)调用会直接返回fasle,即如对列失败
  • 在线程池所有工作者线程都在执行任务(无空闲工作者线程)的情况下,给其提交任务会导致该任务无法被缓存成功。而ThreadPoolExecutor在①任务缓存失败②线程池当前大小未达到最大线程池大小的情况下会创建并启动新的工作者线程。
  • 在极端情况下,给该线程池没提交一个任务都会导致一个新的工作者线程被创建并启动,这会导致系统中线程过多。

2.Executor.newFixedThreadPool()

 该方法的返回值相当于:

new ThreadPoolExecutor(
        nThreads, // 核心线程池大小
        nThreads, // 最大线程池大小
        0, // 此时最大空闲时间没意义
        TimeUnit.MILLISECONDS,
        new LinkedBlockingDeque<Runnable>()
);
  • 空闲工作者线程不会被清理。
  • 线程池大小一旦达到其核心线程池大小就既不会增大也不会减小工作者线程。
  • 不需要此线程池实例时,必须主动将其关闭。

3.Executor.newSingleThreadPool()

 该方法的返回值相当于Executor.newFixedThreadPool(1)所返回的线程池。但该线程池实例并非ThreadPoolExecutor实例,而是一个封装了ThreadPoolExecutor实例且对外仅暴露ExecutorService接口所定义方法的一个ExecutorService实例。

 该线程池确保了在任意时刻只有一个任务会被执行,这就形成类似锁将原本并发的操作改为串行操作的效果。

 该线程池适合用来执行访问了非线程安全对象而又不希望引入锁的任务。

1-2 异步任务的批量执行:CompletionService

 如果需要一次性提交一批异步任务并获取这些任务的处理结果的话,那么仅使用Future接口(ExecutorService.submit(Callable<V> task))会较为繁琐。java.util.concurrent.CompletionService接口能够较便利地批量提交异步任务并获取这些任务的处理结果。

 CompletionService接口定义的一个submit方法可用于提交异步任务,该方法的签名与ThreadPoolExecutor的一个submit方法相同:Future<V> submit(Callable<V> task)。如果需批量提交异步任务,则通过多次调用此submit方法,此时通常并不关心该方法的返回值,若要求批量获取提交的异步任务的处理结果,可以使用CompletionService接口中的

Future<V> take() throws InterruptedException

 该方法与BlockingQueue.take()相似,是一个阻塞方法,其返回值是一个已经执行结束的异步任务对应的Future实例,该实例就是提交相应任务时submit(Callable<V>)调用的返回值。如果take()被调用时没有已经执行结束的异步任务,那么take()的执行线程就会被暂停,直到有异步任务执行结束。因此,我们批量提交了多少个异步任务,则多少次连续调用CompletionService.take()便可以获取这些任务的处理结果。但不保证获取处理结果的顺序与任务提交的顺序一致(与任务完成先后的顺序一致)。

 CompletionServie也定义了两个非阻塞方法用于获取异步任务处理结果:

Future<V> poll()
Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException

 这两个方法与BlockingQueue的poll方法相似,他们的返回值是已经结束任务的异步任务对应的Future实例(如果没拿到则为null)。

 Java标准库提供的CompletionService的实现类为ExecutorCompletionService,ExecutorCompletionService其中一个构造器是:

ExecutorCompletionService(Executor executor,
                BlockingQueue <Future<V>> completionQueue);

由此可见,ExecutorCompletionService相当于Executor实例与BlockingQueue实例的一个融合体。其中Executor负责接收并执行异步任务,而BlockingQueue实例则用于存储已经执行完毕的异步任务对应的Future实例。ExecutorCompletionService会为其客户端提交的每个异步任务(Callable实例或者Runnable实例创建一个相应的Future实例,通过该实例其客户端代码便可以在任务处理完成后获取到相应异步任务的处理结果(如果任务未处理完成,通过不为null的Future拿不到处理结果)。ExecutorCompletionService每执行完一个异步任务,就将该任务对应的Future实例存入其内部维护的BlockingQueue实例之中,其客户端代码则可以通过ExecutorCompletionService.take()调用来获取这个Future实例。

posted @ 2022-12-31 16:03  certainTao  阅读(61)  评论(0编辑  收藏  举报