一、为什么使用线程池
线程是一个操作系统概念。操作系统负责这个线程的创建、挂起、运行、阻塞和终结操作。而操作系统创建线程、切换线程状态、终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情。
另一方面,大多数实际场景中是这样的:处理某一次请求的时间是非常短暂的,但是请求数量是巨大的。这种技术背景下,如果我们为每一个请求都单独创建一个线程,那么物理机的所有资源基本上都被操作系统创建线程、切换线程状态、销毁线程这些操作所占用,用于业务请求处理的资源反而减少了。所以最理想的处理方式是,将处理请求的线程数量控制在一个范围,既保证后续的请求不会等待太长时间,又保证物理机将足够的资源用于请求处理本身。
另外,一些操作系统是有最大线程数量限制的。当运行的线程数量逼近这个值的时候,操作系统会变得不稳定。这也是我们要限制线程数量的原因。
二、线程池基本用法
JAVA语言为我们提供了两种基础线程池的选择:ScheduledThreadPoolExecutor和ThreadPoolExecutor,ScheduledThreadPoolExecutor又是ThreadPoolExecutor的子类。他们都实现Executor、ExecutorService接口。
1、Executor:执行已提交的Runnable任务的对象,并提供一种将任务提交与每个任务将如何运行的机制(包括线程使用的细节、调度等)分离开来的方法。通常使用Executor而不是显式地创建线程。例如,可能会使用一下方法,而不是为一组任务中每个任务调用new Thread(new RunnableTask()).start();
Executor excutor = anExecutor;
excutor.excute(new RunnableTask1());
excutor.excute(new RunnableTask2());
1.1、不过Executor接口并没有严格地要求执行是异步的,在最简单的情况下,执行程序可以在调用的线程中立即运行已提交任务;
class DirectExucutor implements Executor{
public void execute(Runnable r){
r.run();
}
}
1.2、更常见的是,任务是在某个不是调用者线程的线程中执行的。
class ThreadPerTaskExecutor implements Executor{
public void execute(Runnable r){
new Thread(r).start();
}
}
1.3、Executor实现都调度任务的方式和时间强加了某种限制。以下执行程序使任务提交与第二个执行程序保持连续,复合执行程序
class SerialExecutor implements Executor{
final Queue<Runnable> mTasks = new ArrayDeque<Runnable>();
final Executor mExecutor;
Runnable active;
SerialExecutor(Executor executor){
this.mExecutor = executor;
}
public synchronized void execute (final Runnable r){
mTasks .offer(new Runnable(){
public void run(){
try{
r.run();
}finally{
scheduleNext();
}
}
});
if(active == null){
scheduleNext();
}
}
protected synchronized void scheduleNext(){
if((active = mTasks.poll()) != null){
mExecutor.excute(avtive);
}
}
}
2、ExecutorService:目前使用最为广泛的接口,他同时继承Executor的接口(ps:后面重点讲解)。
三、ThreadPoolExecutor
1、构造方法:
ThreadPoolExecutor mTexecutor = new ThreadPoolExecutor(int corePoolSize,int maximunPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory,RejectedExecutionHandler headler)
corePoolSize:线程池中核心线程最大值;如果不进行特别的设定,线程池中始终会保持corePoolSize数量的线程数(不包括创建阶段)。线程池新建线程的时候,如果当前线程总数小于corePoolSize,则新建的是核心线程,如果超过corePoolSize,则新建的是非核心线程,默认情况下会一直存活在线程池中
maximunPoolSize:线程池中线程总数最大值(线程总数 = 核心线程 + 非核心线程)。如果设置corePoolSize参数和设置maximumPoolSzie参数一致,线程池在任何情况下都不会回收空闲线程,keepAliveTime和TimeUnit也就失去了意义。
keepAliveTime:该线程池中非核心线程闲置的时间超过keepAliveTine就会被回收,如果allowCoreThreadTimeOut = true,则会作用核心线程。
TimeUnit:keepAlvieTime的单位,TimeUnit是一个枚举类型。
BlockingQueue<Runnable>:线程池中的任务队列,维护着等待执行的Runnable对象,当所有核心线程都处于非闲置时,新添加的任务会被添加到任务队列中等待处理,如果队列满了,则新建非核心线程执行任务;
常见workQueue类型:
①、SynchronousQueue:一种阻塞队列,其中每个插入操作必须等待另一个线程的移除操作(实现生产者/消费者模型)。
②、ArrayBlockingQueue:由数组支持的有界阻塞队列,此队列按照FIFO(先进先出)原则对元素进行排序;这是一个典型的“有界缓存区”,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建这样的缓存区,就不能再增加其容量。如果试图加入已满的队列,则会导致阻塞;试图从空的队列中提取元素将会导致阻塞。
③、LinkedBlockingQueue:是我们在ThreadPoolExecutor线程池中常用的等待队列,实现是线程安全,实现FIFO的特性,作为生产者消费者首先。它可以指定容量也可以不指定容量。由于它具有“无限容量”的特性,所以我们将它归入无限队列的范畴(实际上任何无限容量的队列/栈都是有容量的,这个容量就是Integer.MAX_VALUE)。它实现是基于链表结构,当队列接受任务时,当前线程数小于核心线程数,则新建线程(核心线程)执行任务;如果等于核心线程数,则进入队列等待。
④、DelayQueue:一种无界阻塞队列,只有延迟期满时,才能执行任务。它是有序的,队头对象延迟到期最长时间,头队列不能为null。
ThreadFactory:创建线程的方式,这是一个接口。
RejectedExecutionHandler :无法由ThreadPoolExecutor执行的任务的处理。抛出异常专用。①、③会由Handler抛出异常。
2、ThreadPoolExcutor策略(当一个任务被添加到线程池时):
①、线程数量未达到corePoolSize,则新建一个核心线程执行;
②、线程数量达到corePoolSize,则将任务加入队列等待;
③、队列已满,新建非核心线程执行任务
④、队列已满,总线线程数有达到maximumPoolSize,就RejectedExecutionHeadler异常抛出。
四、常见四种线程池
1、CacheThreadPool:可缓存线程池
创建方法:
ExecutorService cacheThreadPool = Executors.newCacheThreadPool();
特性:
①、线程数无限制
②、有空闲线程则复用空闲,如无空闲线程则新建线程
③、一定程度减少频繁创建/销毁线程,减少系统开销。
2、 FiexdThreadPool:定长线程池
创建方法:
①、ExecutorService fiexdThreadPool= Executors.newFiexdThreadPool(int nThreads);
②、ExecutorService fiexdThreadPool= Executors.newFiexdThreadPool(int nThreads,ThreadFactory threadFactory);
特性:
①、可控制线程最大并发量(同时执行的线程数)
②、超出的线程会在队列中等待
3、ScheduleThreadPool:定长线程池
创建方法:
ExecutorService scheduleThreadPool= Executors.newScheduleThreadPool(int corePoolSize);
特性:
①、支持定时及周期性任务执行。
4、SingleThreadExecutor:单线程化线程池
创建方法:
ExecutorService singleThreadExecutor= Executors.newSingleThreadExecutor()
特性:
①、有且仅有一个工作线程执行任务;
②、所有任务按照指定顺序执行,遵循FIFO原则。
5、SingleThreadScheduleExecutor:只有一个线程,用来调度执行任务。
创建方法:
ExecutorService singleThreadScheduleExecutor=Executors.newSingleThreadScheduleExecutor();
五、ExecutorService执行
1、execute(Runnable)接受一个Runnable实例,并且异步的执行,没有办法获知task的执行结构。
2、submit(Runnable)返回一个Future对象,通过返回的Future对象,我们可以检查任务是否执行完毕(future.get())。
3、submit(Callable) 返回一个Future对象,但除此之外,还接受Callable,Callable接口中call方法有一个返回值,可以返回任务执行结果,而Runnable接口中run()方法返回void,没有返回值
4、invokeAny(...)接收的是一个Callable的集合,执行这个方法不会返回Future,但是会返回所有Callable任务中其中一个任务执行结果。该方法不能保证返回是那个执行结果。
5、anvokeAll(...)接收一个Callable集合,同时返回Future的List,其中对应着每个Callable任务执行后的Futrue对象。
六、ExecutorService关闭
1、ExecutorService.shutdown():关闭执行任务对象。 在调用shutdown方法之后,ExecutorService不会立即关闭,但是不会接收新的任务,直到当前所有线程任务执行完成才会关闭。
2、ExecutorService.shutdownNow():立即关闭ExecutorService。