线程池
创建线程池
- 通过
ThreadPoolExecutor
手动创建线程池。new ThreadPoolExecutor(…); 推荐!!! - 通过
Executors 执行器
自动创建线程池。Executors.new… ; eg.Executors.newFixedThreadPool();
以上两大类创建线程池的方式,共有 7 种具体实现方法,这 7 种实现方法分别是:
-
Executors.newFixedThreadPool:创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。
-
Executors.newCachedThreadPool:创建一个可缓存的无核心线程的线程池,若线程数超过处理所需,缓存一段时间后会回收,若线程数不够,则新建线程。用于短时间内有突发大量任务的处理场景
-
Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执行顺序。仅一个核心线程。可用于数据库操作,文件操作。
-
Executors.newScheduledThreadPool:创建固定长度线程池,一个可以执行延迟or定时任务的线程池。核心线程数固定,非核心线程数无限,任务队列延迟阻塞。
-
Executors.newSingleThreadScheduledExecutor:创建一个单线程的可以执行延迟任务的线程池。此线程池可以看作是 ScheduledThreadPool 的单线程池版本。
-
Executors.newWorkStealingPool:创建一个抢占式执行的线程池(任务执行顺序不确定),此方法是 JDK 1.8 版本新增的,因此只有在 JDK 1.8 以上的程序中才能使用。
-
ThreadPoolExecutor:最原始、也是最推荐的手动创建线程池的方式,它在创建时最多提供 7 个参数可供设置。
为什么推荐使用ThreadPoolExecutor
ThreadPoolExecutor 相比于其他创建线程池的优势在于,它可以通过参数来控制最大任务数和拒绝策略,让线程池的执行更加透明和可控
ThreadPoolExecutor 的参数
-
corePoolSize核心线程数量(必须)、maximumPoolSize最大线程数量(必须)、keepAliveTime 、TimeUnit、queue、线程工厂、拒绝策略。
-
核心线程数量:线程池会维护最小线程数量,一般就算这些线程空闲也不会销毁,除非设置了allowCoreThreadTime
-
最大线程数量:线程最大数量,减去核心就是救济线程数
-
空闲线程存活时间:当有空闲线程时,而且当前线程数量超过核心线程数量,那么当前线程在一定时间后就会销毁;
-
空闲线程存活时间单位:指定 keepAliveTime 参数的时间单位
-
工作队列:新任务被提交后,会先进入到此工作队列中,任务调度时再从队列中取出任务。
-
线程工厂:创建一个新线程时使用的工厂,可以用来设定线程名、是否为daemon线程等等
-
拒绝策略:当工作队列中的任务已到达最大限制,并且线程池中的线程数量也达到最大限制,拒绝策略,就是解决这个问题的
4种工作队列
-
ArrayBlockingQueue:基于数组的有界阻塞队列,按FIFO排序。新任务进来后,会放到该队列的队尾,有界的数组可以防止资源耗尽问题。当线程池中线程数量达到corePoolSize后,再有新任务进来,则会将任务放入该队列的队尾,等待被调度。如果队列已经是满的,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。
-
LinkedBlockingQuene:基于链表的无界阻塞队列(其实最大容量为Interger.MAX),按照FIFO排序。由于该队列的近似无界性,当线程池中线程数量达到corePoolSize后,再有新任务进来,会一直存入该队列,而基本不会去创建新线程直到maxPoolSize(很难达到Interger.MAX这个数),因此使用该工作队列时,参数maxPoolSize其实是不起作用的。
-
SynchronousQuene:一个不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。也就是说新任务进来时,不会缓存,而是直接被调度执行该任务,如果没有可用线程,则创建新线程,如果线程数量达到maxPoolSize,则执行拒绝策略。
-
PriorityBlockingQueue:具有优先级的无界阻塞队列,优先级通过参数Comparator实现。
4种拒绝策略
-
CallerRunsPolicy:该策略下,在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务。
-
AbortPolicy:该策略下,直接丢弃任务,并抛出RejectedExecutionException异常。
-
DiscardPolicy:该策略下,直接丢弃任务,什么都不做。
-
DiscardOldestPolicy:该策略下,抛弃进入队列最早的那个任务,然后尝试把这次拒绝的任务放入队列
Executors 返回的线程池对象的弊端如下
- FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool:允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。
相比于单个线程数的线程池,为什么不直接创建一个线程?
因为线程池:
- 可以复用线程:即使是单个线程池,也可以复用线程。
- 提供了任务管理功能:单个线程池也拥有任务队列,在任务队列可以存储多个任务,这是线程无法实现的,并且当任务队列满了之后,可以执行拒绝策略,这些都是线程不具备的。
任务提交到线程池,执行过程
当任务被提交到线程池,首先判断是否有空闲线程,如果有则直接交由处理,如果没有则放入队列中,如果队列也满了,直接放给救济线程,救济线程只有在此时是直接拿刚来的任务,后面救济线程只从队列中取;后面的任务来了,先看核心线程,再看队列,若都满了,拒绝策略。
submit()和execute()方法区别
-
submit()方法可以提交Callable和Runnable类型的任务,并且可以返回任务执行的结果。如果任务执行成功,submit()方法会返回一个Future对象,可以通过该对象获取任务执行的结果。如果任务执行失败,submit()方法会抛出异常。
-
execute()方法只能提交Runnable类型的任务,不能返回任务执行的结果。如果任务执行失败,execute()方法会抛出异常。
如果需要获取任务执行的结果,可以使用submit()方法;
如果不需要获取任务执行的结果,可以使用execute()方法。