Java 常用线程池
1.Executor、ExecutorService、ScheduledExecutorService、ScheduledThreadPoolExecutor、ThreadPoolExecutor、Executors的关系
关系说明:
1. Executor是顶级接口,但是它只是执行线程的工具而不是线程池,定义了一个无返回值的execute方法;
2. ExecutorService接口继承了Executor,是真正的线程池接口
3. ScheduledExecutorService继承了ExecutorService,ScheduledThreadPoolExecutor继承了
ThreadPoolExecutor,实现了ScheduledExecutorService
4.ThreadPoolExecutor实现了ExecutorService
5.Executors类里面提供了一些静态工厂,生成一些常用的线程池。
2.常用线程池
-
ThreadPoolExecutor的构造方法
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
各个参数的含义:
corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:当线程池中存活的线程数超过了核心线程数,且这些多余的线程为空闲线程,这个参数代表这些空间线程的最长存活时间,超过这个时间就会被销毁
unit:存活时间的单位
workQueue:任务队列,将用户提交的任务加入到任务队列中
对工作队列的说明:
工作队列是代表提交待执行任务的队列。它是BolckingQueue接口的对象,仅用于存储Runnable任务。根据任务功能不同,可以在ThreadPoolExecutor构造方法中使用如下几种阻塞队列:
-
直接提交队列:由synchronousQueue对象提供,该队列没有容量,提交给线程池的任务不会被真实保存,总是将新任务交给线程执行,如果没有空余线程,就尝试创建新的线程,如果线程达到maximumPoolSize 指定的最大线程,就执行拒绝策略。
-
有界队列:由ArrayBlockingQueue实现(先入先出),在创建ArrayBlockingQueue对象时,可以指定一个容量。当有任务需要执行时,且现在线程池中的线程数小于核心线程数就创建线程,如果超过了核心线程数则放入等待队列;如果队列已满,则在线程池中线程数小于maximumPoolSize 的情况下创建新的线程;如果超过了maximumPoolSize 的大小,则执行拒绝策略。
-
无界队列:由LinkedBlockingQueue对象实现(先入先出),与有界队列相比,除非系统资源耗尽,否则不会存在任务入队失败的情况;当有新任务时,在系统线程小于corePoolSize时创建线程;当大于corePoolSize则把任务加入阻塞队列(因为不存在阻塞队列会满的情况,因此不用像有界队列一样,当阻塞队列满了以后继续创建线程)
-
优先任务队列:通过PriorityBlockingQueue实现,通过任务优先级先后顺序执行。
threadFactory:线程工厂,用于创建线程
handle:拒绝策略,当任务太多,线程池不能处理时,怎么应对这样的情况
ThreadPoolExecutor构造方法的最后一个参数表示拒绝策略。当提交给线程迟的任务量超过了实际承载能力时如何处理?也就是说线程池的线程已经用完了,等待队列也满了,无法为新的任务提供服务,那么就采用拒绝策略来解决这个问题,JDK提供了四种拒绝策略:
- AbortPolicy:抛出异常(JDK中默认选择的方式)
- CallerRunsPolicy:只要线程池没关闭,会在调用者线程运行当前负载的任务
- DiscardOldestPolicy:只要线程池没关闭,将队列中最老的任务丢弃,并再次尝试提交新任务
- DiscardPolicy:直接丢弃
Executors中定义的三个线程池:
-
-
newSingleThreadPool():单线程复用的线程池
public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService //通过ThreadPoolExecutor产生的线程池,核心线程数和最大线程数都是1 //无界队列 (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }
特点:使用单个工作线程执行任务
-
newFixedThreadPool():固定大小的线程池
-
public static ExecutorService newFixedThreadPool(int nThreads) { //核心线程数和最大线程数都是n //采用无界队列 return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }
特点:特点就是可以重用固定数量线程的线程池
-
newCachedThreadPool():创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,
那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
-
public static ExecutorService newCachedThreadPool() { //核心线程数为0,最大线程数为Integer.MAX_VALUE //采用直接提交队列 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }
特点:
-
如果线程池中的线程数超过实际任务需要,在60s的时间后这些空闲线程会被回收;若当前线程数低于实际任务需要,则会不断创建线程
-
SynchronousQueue队列是一个直接提交队列,它是一个没有容量的阻塞队列。每个插入操作必须等待另一个线程的对应移除操作。这意味着,如果主线程提交任务的速度高于线程池中处理任务的速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU资源。
执行过程如下:
1.首先执行SynchronousQueue.offer(Runnable task)。如果在当前的线程池中有空闲的线程正在执行SynchronousQueue.poll(),那么主线程执行的offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行。,execute()方法执行成功,否则执行步骤2
2.当线程池为空(初始maximumPool为空)或没有空闲线程时,配对失败,将没有线程执行SynchronousQueue.poll操作。这种情况下,线程池会创建一个新的线程执行任务。
3.在创建完新的线程以后,将会执行poll操作。当步骤2的线程执行完成后,将等待60秒,如果此时主线程提交了一个新任务,那么这个空闲线程将执行新任务,否则被回收。因此长时间不提交任务的CachedThreadPool不会占用系统资源。
说明:SynchronousQueue是一个不存储元素阻塞队列,每次要进行offer操作时必须等待poll操作,否则不能继续添加元素。
-