java并发编程之二、分工
一、Thread Runnable Callable Future FutureTask
这几个类型是并发任务在java中最基本的映射,Thread以及Runnable以及Callable毋庸赘言了,Future对应的是一个异步任务的引用,可以用来获取一个异步任务的结果,也可以用来判断一个异步任务是否执行结束,还可以用来取消一个异步任务的执行,而FutureTask同时继承了Future和Runnable,所以作为一个工具类,他完全可以用来取代Runnable的使用,而且还更方便。
二、ThreadPoolExecutor
虽然我们认为线程是比进程轻量级的东西,但频繁的创建和销毁线程也是非常耗费资源的,所以一般建议在频繁使用线程的地方,尽量使用线程池。在java中可以通过Executors的静态方法来获取相应的线程池,既可以是一般的ExecutorService,也可以是具有定时功能的ScheduledExecutorService,这两个接口都提供了提交异步任务的接口,但隐藏了底层线程池的创建逻辑,但线程池的最优实践一般建议我们自定义自己的线程池,这样你可以更清楚自己所使用的线程池的特性,比如采用何种队列,线程的存在时间,线程的名称等。这里先回顾一下创建线程池的几个核心参数。
- 构建
- corePoolSize,线程池的最小线程数,可以评估一个线程池的日常使用的线程数。
- maximumPoolSize,线程池的最大线程数,假如业务面临一个小高峰,则需要新建线程,但也不能多于这个参数。
- keepAliveTime&unit,定义当一个线程闲置了多长时间需要回收,比如业务小高峰过去了,不会立刻回收线程,而是需要等待一段时间。
- workQueue,这个阻塞队列也是很重要的一个参数,采用有界队列还是无界队列,默认的队列很多都是无界队列,如果无限制的加入队列,也会导致OOM等其他问题。
- threadFactory,通过线程工厂,你可以给线程池创建的线程一些默认的属性,比如线程的名称,有助于后期调试时识别。
- handler,定义当线程池已满并且队列已满(有界队列)时,如何处理提交的异步任务,包括AbortPolicy(拒绝任务并抛出异常)、CallerRunsPolicy(在调用者自己的线程执行任务)、DiscardOldestPolicy(抛弃最久的任务然后再次尝试提交)和DiscardPolicy(静默抛弃任务)。
- 异常,线程池异常处理的最佳实践是总是在使用线程池的地方使用try/catch来捕获最多的异常,而不仅仅是依赖线程池自己的异常处理逻辑。
三、ForkJoinPool
针对普通的线程池应用,使用ThreadPoolExecutor就可以了,但是如果任务具有分治的特点,则推荐使用ForkJoinPool来提交,他有自己的一套机制,能够更高效的执行任务。
在ForkJoinPool中一般通过ForkJoinTask来提交任务,而在实现中一般通过ForkJoinTask 的两个抽象子类——RecursiveAction 和 RecursiveTask来构建ForkJoinTask,其中RecursiveAction没有返回值,而RecursiveTask具有返回值,可以根据需要进行选择。
四、CompletableFuture
CompletableFuture是在java 1.8中提出的同步转异步的工具类,具有非常强大的表达能力,通过使用CompletableFuture你能够像编写同步代码一样编写异步代码,由于功能强大,所以学习起来有点麻烦,但一旦真的学会了,相信会让你不由得赞叹。需要注意的一点是,CompletableFuture默认使用ForkJoinPool作为线程池,也可以自己提供线程池。
- 构建,CompletableFuture提供了若干的静态方法,通过静态方法可以很容易的构建相应的实例。
- 表达,CompletableFuture提供了很多表达语句,汇总之后可以分为下面几类:
- 串行,主要是 thenApply、thenAccept、thenRun 和 thenCompose 这四个系列的接口,表达的是执行完一个异步任务之后执行下一个异步任务。
- 汇聚,主要是 thenCombine、thenAcceptBoth 和 runAfterBoth 系列的接口,表达的是两个异步任务执行完之后执行下一个任务。
- 异或,主要是 applyToEither、acceptEither 和 runAfterEither 系列的接口,表达的是两个异步任务任何一个执行完之后执行下一个任务。
- 异常,异步编程中无法使用try/catch机制来捕获异常,不过CompletableFuture同样提供了exceptionally、whenComplete和handle三个接口来使用,exceptionally明显是用在异常处理中,而whenComplete相当于语言中的finally,而handle在于whenComplete相同功能的同时支持返回结果。
五、CompletionService
CompletionService是Java提供的并发执行的工具类,它解耦了异步任务的提交者和异步任务结果的消费者,也是一种producer-consumer的实现。使用CompletionService的典型场景如下:
- 批量异步提交,批量异步获取,异步任务的生产者只负责向线程池中提交任务,不负责任务的处理,而异步结果的消费者,可以方便的拿到所有异步任务的结果,并且异步结果的获取严格依赖于异步任务完成的时间,即消费者会按照异步任务的完成时间拿到相应的异步处理结果,这在某些场景下会非常有效。
- 批量异步提交,获取最快结果,这个场景其实是对上面场景的一个深化,虽然通过CompletionService可以拿到所有的异步结果,但是对于某些操作,可能只需要最快返回的那个,其余的异步操作就可以取消了。比如在服务端同时在几个数据中心进行查询,最先返回的结果会被使用,而其他请求会被取消。