线程池Executor
目录
1、线程池简介
线程池是在java开发中很重要的一个概念,Android中的线程池和和java是保持一致的,并无什么区别。线程池是一个抽象的概念,在java中是一个接口类,用Executor表示, 具体实现类为ThreadPoolExecutor,它们位于java.util.concurrent包下面,这个接口类很短,而且接口声明就只有一个,并且注释中说明了一些情况怎么使用,
package java.util.concurrent;
public interface Executor {
/**
* Executes the given command at some time in the future. The command
* may execute in a new thread, in a pooled thread, or in the calling
* thread, at the discretion of the {@code Executor} implementation.
*
* @param command the runnable task
* @throws RejectedExecutionException if this task cannot be
* accepted for execution
* @throws NullPointerException if command is null
*/
void execute(Runnable command);
}
上面省略了注释,可以看注释,很管用,上面有模版,教我们怎么创建一个自己线程池,其中有段是这么说的:许多线程池的实现类对将要执行的任务的方式和时间做了限制,下面展示了串型线程池在执行任务的时候,从一个任务到下一个任务的过程。
下面代码来至注释中的举例,说明了一个从任务如何进入到下一个任务的串行执行过程:
package demo.xx.patten.xx;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Executor;
public class SerialExecutor implements Executor {
final Queue<Runnable> tasks = new ArrayDeque<>();
final Executor executor;
Runnable active;
public SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
tasks.add(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (active == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((active = tasks.poll()) != null) {
executor.execute(active);
}
}
}
上面的SerialExecutor管理了一个Runnable对象的任务列队tasks和一个从构造函数中传入的线程池,每次调用SerialExecutor的execute方法的时候都会传入一个Runnable对象,然后将这个runnbale的执行方法run方法包装进一个新建的Runnable对象中,然后将这个新建的Runnbale对象插入tasks队列中。紧接着由于初始化的时候active是null,于是会首先执行scheduleNext()方法,这个时候通过tasks.poll()方法将插入的任务,也就是Runnable对象取出来交给构造函数中传入的Executor对象实例来执行,注意上面try的finally块中在r.run()被执行后都会被调用,这就保证了从一个任务到下一个任务的传递,因为active在第一执行scheduleNext后会被负值,之后就不等于null了,那么判断语句就不会被执行了。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
}
- corePollSize: 线程池的核心线程数的最大数量,默认情况下,核心线程会在在线程中一直存活,即使处于闲置状态。但如果将ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,则核心线程数在闲置一段时间后,会被回收,这个事件由keepAliveTime指定;
- maxinumPoolSize: 线程池锁容纳的最大线程数,当活动线程达到这个数后,新来的任务将被阻塞;
- keepAliveTime: 非核心线程闲置的最大时长,超过后就会被回收,当allowCoreThreadTimeOut属性设置为true后,闲置的核心线程超过这个事件也会被回收;
- unit: 用于指定keepAliveTime参数的事件单位,这是一个枚举类型,常用的有TimeUnit.MILLISECONDS, TimeUnit.SECONDS, TimeUnit.MINUTES等;
- workQueue: 线程池的任务队列,通过线程池的execute方法提交的Runnable对象会存储在这个队列中;
- threadFactory: 线程工厂, 为线程池提供创建新线程的功能,ThreadFactory只是一个接口,也只有一个方法: Thread newThread(Runnable r);
2、线程池的好处
- 线程复用,避免现线程创建和销毁带来的的性能开销;
- 有效控制线程池的最大并发数,避免大量线程之间抢夺资源造成阻塞;
- 能够对线程进行简单的管理,并提供定时执行及指定间隔循环等功能;
3、线程池执行任务的规则
- 如果线程池中线程数量未达到核心线程的数量,会直接启动一个核心线程去执行任务;
- 如果线程池中线程的数量已经达到或者超过了核心线程的数量,那么接下来的任务会被插入到任务列队(workQueue)中排队等待执行;
- 如果workQueue中线程数量达到了列队的上线,导致任务无法再插入任务列队,并且线程池中的线程的数量没有达到线程池设定的线程数上限,这种情况下会启动非核心线程来执行任务;
- 如果步骤3中线程数量达到线程池规定最大值(maxmumPoolSize),那么线程池会拒绝执行新添加的任务,并且ThreadPoolExecutor会调用RejectedExecutionHandler的的rejectedExecution方法来通知调用者。
4、线程池的分类
从前面的介绍我们了解到,要直接创建一个线程池,需要传入很多个参数,这在实际使用中,就会显得比较麻烦,所以在Executors这个线程池工具类中,给我们提供了更加简单的创建线程池的API, 主要有四种类型,注意仅仅是类型,并不是存在的java类
1)FixedThreadPool类型
通过Executors.newFixedThreadPool(int nThreads)创建
创建一个固定线程数量的线程池,线程池中的所有线程都是核心线程,当线程空闲时不会被回收,也不会超时,除非线程池被关闭,当线程池中线程都处于活动状态的时候,新进任务会处于等待状态,直到有线程空出来去处理,处于等待状态的任务会在任务列队中排队等候,这个列队的容量没有大小限制,由于线程不会被回收,这样便能够快速的响应外界请求。
2)CachedThreadPool类型
通过Executors.newCachedThreadPool()创建
这是一个线程池的线程的数量是不定的,所有线程都是非核心线程,最大线程数量为Integer.MAX_VALUE,当线程池中线程都处于活动状态的时候,线程池会创建新的线程池来处理新进任务,否则就会利用空闲线程来处理新进任务,线程池中的线程都有60s的超时机制,超过时间的线程会被回收。
使用场景:大量的耗时较少的任务
3)ScheduleThreadPool类型
通过Executors.newScheduleThreadPool(int corePoolSize)创建。
这种线程池的核心线程数是固定的,非核心线程数量没有限制,并且非核心线程执行完任务会被立刻回收,非核心线程没有闲置的状态。
使用场景:定时任务和固定周期的重复任务
4)SingleThreadExecutor类型
通过Executors.newSingleThreadExecutor()创建
SingleThreadExecutor内部只有唯一一个核心线程用来执行任务,其确保了所有的任务都在一个线程中执行,SingleThreadExecutor存在的意义在于统一所有的外界任务到一个线程中去执行,这样这些任务就不需要处理线程同步的问题。