Java并发之Executors简介
Java并发之Executors简介
1. Executor、ExecutorService和ScheduleExecutorService
Java并发框架提供了三个主要的Executor接口Executor、ExecutoService和ScheduleExecutorService。三个接口的继承关系如下图所示
- Executor一个支持启动线程任务的简单接口;
- ExecutorService,是Executor接口的子接口,增加了额外的特性例如管理线程任务和自身executor的生命周期;
-
ScheduleExecutorService,是ExecutorService的子接口,从其名字可以看出,支持定时,周期性的运行线程任务;
-
Executor接口提供一个execute方法,用于提供一个方法来启动运行线程任务
e.execute(r)
,同new Thread(r).start()
功能差不多,通常execute()方的的实现是利用一个存在的工作线程worker来运行一个Runnable或者将Runnable入队到一个队列来等待工作线程worker可用 -
ExecutorService接口在execute基础上提供了一个更万能的方法submit方法,此方法不仅可以接收Runnable还可以接收Callable对象,该对象可以返回一个任务运行后返回的值。submit方法返回一个Future对象,可以用于取到一个Callable对象和管理Callable和Runnable对象的状态。其中Callable和Future通常是配合在一起使用的。
- ScheduleExecutorService接口额外的增加一个schedule方法,接收Runnable和Callable对象并同时给定时的时间;scheduleAtFixedRate 和 scheduleWithFixedDelay以一定的频率重复的执行某个Runable.
-
2. 几种线程池ThreadPollExecutor、ScheduleThreadPollExecutor和ForkJoinPool
Executor的实现主要是采用线程池来实现的,使用线程池可以显著的减少线程创建的次数,从而减少线程创建带来的额外的开销。Java包提供了几种线程池方式实现的Executor,例如ThreadPollExecutor、ScheduleThreadPollExecutor和ForkJoinPool。这三种都是用途不同的Executor,但是都采用线程池的方式来实现的。
由于直接使用比较困难繁琐,JDK提供了一个工厂类Executors,包含几个重要的创建不同用途的、可配置线程池的静态方法,如下图所示:
3. 生产者-消费者模型
生产者消费者模型在日常的编程中使用得比较多,主要适用于一个模块产生数据,另一个模块消费数据的情况。例如,常见的流水线(pipline)作业(考虑web服务器,有专门接受请求的线程,可以视为生产者;有专门的请求处理的线程,可以视为消费者;这样的模型类似工厂的流水线作业)。作为实际编程中,生产-消费模型还有一个必要的要素,就是缓冲区。缓冲区主要有两个主要的用途:第一是解耦;第二是匹配生产者和消费者的速率(只要是缓冲区,这个作用都是最基本的)。
生产-消费者模型的示意图如下所示:
缓存区通常采用队列这一数据结构,只要队列不满,生产者就不停的将数据插入队列;只要队列不空,消费者就不停的从队列提取数据;如果队列满或空,生产者、消费者就相应的睡眠,等待被唤醒。这里就涉及到生产者-消费者同步问题。由于生产者、消费者是不同的线程(或者进程),因此在进行存取缓存区的时候会存在同步互斥、并发访问的问题。因此Java在并发包中引入了BlockingQueue
等并发访问的类。
在Android开发中的开源轮子Picasso
(一个图片解析缓存库)中,就采用的生产者-消费者模型,对图片的请求由单独的分发线程(dispatcher
)插入缓冲队列中,然后有专门的图片解析线程池(hunter
)(消费者)去处理(消费)这个请求,最后将解析完成的图片完成交与UI显示。
生产者-消费者队列模型应用有大有小;大到分布式系统的采用消息队列进行同步,小到具体系统的某一个任务处理模块。模型思想很朴素,但是应用及其变体却很丰富,自己在设计的时候还需慢慢品味。