Executor 框架与线程池

Posted by alonealice on 2016-11-04

Executor 框架是 Java 5 中引入的,其内部使用了线程池机制,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。通过 Executor 来启动线程比使用 Thread 的 start 方法更好。首先是能够降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。其次能够提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。最后还能提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
Executor 接口中之定义了一个方法 execute(Runnable command),该方法接收一个 Runable 实例,它用来执行一个任务,任务即一个实现了 Runnable 接口的类。ExecutorService 接口继承自 Executor 接口,它提供了更丰富的实现多线程的方法。
ExecutorService 的生命周期包括三种状态:运行、关闭、终止。创建后便进入运行状态,当调用了 shutdown()方法时,便进入关闭状态,此时意味着 ExecutorService 不再接受新的任务,但它还在执行已经提交了的任务,当素有已经提交了的任务执行完后,便到达终止状态。
Executors 提供了一系列工厂方法用于创先线程池,返回的线程池都实现了 ExecutorService 接口,我们可以使用它来创建ExecutorService实例。
提供的方法主要有以下几个:

public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池。调用execute时将重用以前构造的可用的线程。如果现有线程没有可用的,则创建一个新线程并添加到池中。在缓存中如果有60s都未被使用的线程将会被移除,它最多可以Integer.MAX_VALUE个线程。

public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。它与newCachedThreadPool差不多,但是它最多只能有nThreads个线程,所以它不能随时创建新的线程,当线程池满时,需要创建新的线程时会在队列中等待,知道有线程执行完成并且从池中移除。

public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor,其在任何时候线程池中都只有一个线程,执行完之后会移除。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

Executor 运行 Runnable 任务

在获取上述的几个实例后,我们就可以调用实例的execute方法运行runnable方法。
示例:

1
2
3
4
5
6
7
8
9
10
ExecutorService executor= Executors.newCachedThreadPool();
executor.execute(new Runnable() {
@Override
public void run() {
while(true) {
num1++;
Log.d("num1", num1 + "");
}
}
});

Executor 运行 Callable 任务

在使用线程的过程中,我们经常会遇到由于没有返回值而带来的问题。而在使用ExecutorService的时候,我们就可以运行Callable接口来获得返回值。Callable 的 call()方法可以通过 ExecutorService 的 submit(Callable task) 方法来执行,并且返回一个 Future,同样,将 Runnable 的对象传递给 ExecutorService 的 submit 方法,则该 run 方法自动在一个线程上执行,并且会返回执行结果 Future 对象,但是在该 Future 对象上调用 get 方法,将返回 null。
示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Callable<String>s=new Callable<String>() {
@Override
public String call() throws Exception {
for(int i=0;i<100000;i++){
num1++;
}
return num1+10+"";
}
};
Future<String>f= executor.submit(s);
try {
Log.d("最后的结果",f.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}

自定义线程池

除了使用自带的线程池,我们也可以自定义线程池来执行任务。自定义线程池主要是使用ThreadPoolExecutor类创建。其构造方法如下:
public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue<Runnable> workQueue)

当中参数的含义如下:
corePoolSize 线程池中的最少线程数,当添加新任务时,当池中的线程少于corePoolSize,即使有空闲线程,也会新建线程添加到池中。
maximumPoolSize 线程池中最大的线程数。
keepAliveTime 空闲线程的保持时间
unit 时间单位(秒/分…)
workQueue 任务执行前保存任务的队列

所以当有新任务要处理时,先看线程数是否大于corePoolSize,再看缓冲队列是否满,最后看线程数是否大于最大maximumPoolSize。

不同缓冲队列的区别

SynchronousQueue:它将任务直接提交给线程处理,当不存在空闲线程时会创建新线程,所以maximumPoolSizes应设置为Integer.MAX_VALUE,已避免提交任务失败,newCachedThreadPool就是采取这种队列。
LinkedBlockingQueue:队列的容量是无限的,所以线程中最大线程数不会超过corePoolSize,newFixedThreadPool就是采用这种队列。
ArrayBlockingQueue:有界队列,队列中的任务数量有限,这样可以防止资源过度消耗。
示例:

1
2
3
4
5
6
7
8
ThreadPoolExecutor r=new ThreadPoolExecutor(3,5,50, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(20));
r.execute(new Runnable() {
@Override
public void run() {
num1++;
Log.d("num1", num1 + "");
}
});