在上一篇文章中,我们学习了线程池的构造方法以及基本的使用方法。还没有看过上一篇文章的朋友,建议先去阅读 Android 线程池的使用,本文将继续深入了解线程池的高级用法。
BlockingQueue 的接口队列
在多数情况下,我们构建线程池主要是通过 Executors 的工厂方法来创建线程池,但 Executors 的工厂方法创建的线程池也是直接或间接通过配置 ThreadPoolExecutor 参数来实现的。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...
}
在上面的参数中理解起来都比较简单,但 workQueue 要多说明一下。workQueue 是一个 BlockingQueue 对象,它是一个特殊的队列,当我们从 BlockingQueue 中取数据时,如果 BlockingQueue 是空的,则取数据的操作会进入到阻塞状态,当 BlockingQueue 中有了新数据时,这个取数据的操作又会被重新唤醒。同理,如果 BlockingQueue 中的数据已经满了,往 BlockingQueue 中存数据的操作又会进入阻塞状态,直到 BlockingQueue 中又有新的空间,存数据的操作又会被重新唤醒。它的泛型限定它是用来存放 Runnable 对象的。不同的线程池它的任务队列实现是不一样的,所以保证不同线程池不同功能的核心就是这个 workQueue 的实现了,通过 Executors 的工厂方法创建的线程池传入的 workQueue 也是不一样的。接下来就来说明 BlockingQueue 的多种不同的实现类。
了解了 BlockingQueue 的种类,再来看看各种不同线程池分别传入的 workQueue 的类型:
1、newFixedThreadPool()—>LinkedBlockingQueue
2、newSingleThreadExecutor()—>LinkedBlockingQueue
3、newCachedThreadPool()—>SynchronousQueue
4、newScheduledThreadPool()—>DelayedWorkQueue
自定义线程池 ThreadPoolExecutor
一般来说,Java 内置的线程池已经够我们使用了,不过有时候我们也可以根据需求来自定义线程池,而要自定义不同功能的线程池,归根结底还是要根据 BlockingQueue 的实现,所以我们要自定义线程池,就必须从 BlockingQueue 着手,而上面说了 BlockingQueue 的实现类有多个,那么我们这次就选用 PriorityBlockingQueue 来实现一个按任务的优先级来处理的线程池。
1. 首先我们创建一个基于 PriorityBlockingQueue 实现的线程池.
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new PriorityBlockingQueue());
public abstract class PriorityRunnable implements Comparable ,Runnable{
private int priority;
public PriorityRunnable(int priority) {
if (priority < 0)
throw new IllegalArgumentException();
this.priority = priority;
}
@Override
public int compareTo(PriorityRunnable another) {
int my = this.getPriority();
int other = another.getPriority();
return my < other ? 1 : my > other ? -1 : 0;
}
@Override
public void run() {
doSth();
}
public abstract void doSth();
public int getPriority() {
return priority;
}
}
ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 3, 0, TimeUnit.SECONDS, new PriorityBlockingQueue());
for (int i = 0; i < 30; i++) {
final int priority = i;
PriorityRunnable runnable = new PriorityRunnable(priority) {
@Override
public void doSth() {
String threadName = Thread.currentThread().getName();
Log.v(TAG, "线程:" + threadName + ",正在执行优先级为:" + priority + "的任务");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
executor.execute(runnable);
}
}
测试效果
我们看下刚刚自定义的线程池是否达到了我们想要的功能,即根据任务的优先级进行优先处理任务,效果如下:
可以从执行结果中看出,由于核心线程数设置为 3,刚开始时,系统有 3 个空闲线程,所以无须使用任务队列,而是直接运行前三个任务,而后面再提交任务时由于当前没有空闲线程所以加入任务队列中进行等待,此时,由于我们的任务队列实现是由 PriorityBlockingQueue 实现的,所以进行等待的任务会经过优先级判断,优先级高的放在队列前面先处理。从效果图中也可以看到后面的任务是先执行优先级高的任务,然后依次递减。
优先级线程池的优点
从上面我们可以得知,创建一个优先级线程池非常有用,它可以在线程池中线程数量不足或系统资源紧张时,优先处理我们想要先处理的任务,而优先级低的则放到后面再处理,这极大改善了系统默认线程池以 FIFO 方式处理任务的不灵活.
扩展线程池ThreadPoolExecutor
除了内置的功能外,ThreadPoolExecutor 也向外提供了三个接口供我们自己扩展满足我们需求的线程池,这三个接口分别是:
1. beforeExecute() - 任务执行前执行的方法
2. afterExecute() -任务执行结束后执行的方法
3. terminated() -线程池关闭后执行的方法
这三个方法在 ThreadPoolExecutor 内部都没有实现,我们也可以通过自定义 ThreadPoolExecutor 来实现这个功能。
public class MyThreadPool extends ThreadPoolExecutor {
private static final String TAG = "Bradley" ;
public MyThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
Log.d(TAG, "beforeExecute: 开始执行任务!");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
Log.d(TAG, "beforeExecute: 任务执行结束!");
}
@Override
protected void terminated() {
super.terminated();
Log.d(TAG, "terminated: 线程池关闭!");
}
}
优化线程池 ThreadPoolExecutor
虽说线程池极大改善了系统的性能,不过创建线程池也是需要资源的,所以线程池内线程数量的大小也会影响系统的性能,大了反而浪费资源,小了反而影响系统的吞吐量,所以我们创建线程池需要把握一个度才能合理的发挥它的优点,通常来说我们要考虑的因素有 CPU 的数量、内存的大小、并发请求的数量等因素,按需调整。
通常核心线程数可以设为CPU数量+1,而最大线程数可以设为CPU的数量*2+1。
获取CPU数量的方法为:
Runtime.getRuntime().availableProcessors();
shutdown()和shutdownNow()的区别
关于线程池的停止,ExecutorService 为我们提供了两个方法:shutdown 和 shutdownNow,这两个方法各有不同,可以根据实际需求方便的运用,如下:
1、shutdown() 方法在终止前允许执行以前提交的任务。
2、shutdownNow() 方法则是阻止正在任务队列中等待任务的启动并试图停止当前正在执行的任务。
参考资料
Android性能优化之使用线程池处理异步任务
Android开发之线程池使用总结