阿里巴巴Java开发手册编程规约第六节:
- 【强制】线程资源必须通过线程池提供,不允许在应用中自行显示创建
- 【强制】线程池不允许使用Executer去创建,而是通过TreadPoolExecuter的方式,这样的处理方式让写程序的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors利用工厂模式向我们提供了4种线程池实现方式,但是并不推荐使用,原因是使用Executors创建线程池不会传入这个参数而使用默认值所以我们常常忽略这一参数,而且默认使用的参数会导致资源浪费,不可取。
先介绍一下线程池体系
Java.util.concurrent.Executor 负责线程的使用与调度的根接口
ExecutorService:Executor的子接口,线程池的主要接口
AbstractExecutorService:实现了ExecutorService接口基本实现了ExecutorService其中声明的所有方法添加其他方法
ThreadPoolExecutor:继承了AbstractExecutorService,主要的常用实现类
ScheduledExecutorService:继承了ExecutorService,负责线程调度的接口
ScheduledThreadPoolExecutor:继承了ThreadPoolExecutor同时实现了ScheduledExecutorService
ThreadPoolExecutor
ThreadPoolExecutor是线程池的真正实现,他通过构造方法的一系列参数,来构成不同配置的线程池。常用的构造方法有下面四个:
-
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
-
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory)
-
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)
-
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
构造方法参数说明
-
corePoolSize
核心线程数,默认情况下核心线程会一直存活,即使处于闲置状态也不会受存keepAliveTime限制。除非将allowCoreThreadTimeOut设置为true。
-
maximumPoolSize
线程池所能容纳的最大线程数。超过这个数的线程将被阻塞。当任务队列为没有设置大小的LinkedBlockingDeque时,这个值无效。
-
keepAliveTime
非核心线程的闲置超时时间,超过这个时间就会被回收。
-
unit
指定keepAliveTime的单位,如TimeUnit.seconds。当将allowCoreThreadTimeOut设置为ture时对corePoolSize生效。
-
workQueue
线程池中的任务队列.
常用的有三种队列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
-
threadFactory
线程工厂,提供创建新线程的功能。ThreadFactory是一个接口,只有一个方法
public interface ThreadFactory { Thread newThread(Runnable r); }
通过线程工厂可以对线程的一些属性进行定制。
默认的工厂:
static class DefaultThreadFactory implements ThreadFactory { private static final AtomicInteger poolNumber = new AtomicInteger(1); private final ThreadGroup group; private final AtomicInteger threadNumber = new AtomicInteger(1); private final String namePrefix; DefaultThreadFactory() { SecurityManager var1 = System.getSecurityManager(); this.group = var1 != null?var1.getThreadGroup():Thread.currentThread().getThreadGroup(); this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-"; } public Thread newThread(Runnable var1) { Thread var2 = new Thread(this.group, var1, this.namePrefix + this.threadNumber.getAndIncrement(), 0L); if(var2.isDaemon()) { var2.setDaemon(false); } if(var2.getPriority() != 5) { var2.setPriority(5); } return var2; } }
-
RejectedExecutionHandler
RejectedExecutionHandler也是一个接口,只有一个方法
public interface RejectedExecutionHandler { void rejectedExecution(Runnable var1, ThreadPoolExecutor var2); }
当线程池中的资源已经全部使用,添加新线程被拒绝时,会调用RejectedExecutionHandler的rejectedExecution方法。
线程池规则
线程池的线程执行规则跟任务队列有很大的关系。
-
下面都假设任务队列没有大小限制:
- 如果线程数量<=核心线程数量,那么直接启动一个核心线程来执行任务,不会放入队列中。
- 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是LinkedBlockingDeque的时候,超过核心线程数量的任务会放在任务队列中排队。
- 如果线程数量>核心线程数,但<=最大线程数,并且任务队列是SynchronousQueue的时候,线程池会创建新线程执行任务,这些任务也不会被放在任务队列中。这些线程属于非核心线程,在任务完成后,闲置时间达到了超时时间就会被清除。
- 如果线程数量>核心线程数,并且>最大线程数,当任务队列是LinkedBlockingDeque,会将超过核心线程的任务放在任务队列中排队。也就是当任务队列是LinkedBlockingDeque并且没有大小限制时,线程池的最大线程数设置是无效的,他的线程数最多不会超过核心线程数。
- 如果线程数量>核心线程数,并且>最大线程数,当任务队列是SynchronousQueue的时候,会因为线程池拒绝添加任务而抛出异常。
-
任务队列大小有限时
- 当LinkedBlockingDeque塞满时,新增的任务会直接创建新线程来执行,当创建的线程数量超过最大线程数量时会抛异常。
- SynchronousQueue没有数量限制。因为他根本不保持这些任务,而是直接交给线程池去执行。当任务数量超过最大线程数时会直接抛异常。