创建线程池
首先看看线程池的构造方法:
public ThreadPoolExecutor(
//线程池核心线程数最大值
int corePoolSize,
//线程池中的线程数量最大值
int maximumPoolSize,
//线程池中非核心线程空闲的存活时间大小
long keepAliveTime,
//上个参数的时间单位
TimeUnit unit,
//存放待运行任务的阻塞队列
BlockingQueue<Runnable> workQueue,
//创建线程的工厂,用来初始化线程(指定名称等)
ThreadFactory threadFactory,
//线程池饱和后的拒绝策略,在后面会讲解
RejectedExecutionHandler handler
)
- newSingleThreadExecutor()
线程数量始终为1,而队列是无界的,能保证所有的任务按照顺序执行
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
- newCachedThreadPool()
线程会被缓存(可以重用),当没有缓存的线程可以用的时候,就需要创建新的工作线程。线程运行完任务后,有60s的时间等待新的任务(等待到了就去执行,相当于这个线程被复用了),如果60s过了,则线程会被销毁。长时间闲置时,此线程池不会销毁什么资源。
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
}
- newFixedThreadPool()
线程池里,最多有nThreads数量的线程在运行,所一,当一个任务被提交,但是运行中的线程数量已经达到nThreads,就会被添加到队列中,直到那些运行中的线程有完成任务并退出了,这个任务才会被创建线程,补足nThreads。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
此线程池,没有非空闲时间,即keepAliveTime为0,所以线程不会被复用,而是直接销毁。线程池能保证同时运行的线程数量是固定的(Fixed)。
- newScheduledThreadPool()
顾名思义,是一个可以调度(间隔性或者周期性)的线程池。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
以上的构造函数里,添加了DelayedWorkQueue作为队列,故可以进行调度。
线程池的执行,结合其中的工作队列来实现功能
下面将给出线程池基本的工作流程(一个最基础的自定义的线程池的能力):
当我们手动创建一个线程池的时候,例如 用以下参数构造:
new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(),
threadFactory);
着重看一下一个参数:60L,这个时间参数。构造方法里的解释是:
@param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
意思是当线程数大于内核数时,这是多余的空闲线程将在终止之前等待新任务的最长时间。
所以当这个参数被设置为0的时候,线程完成任务后,就不会是所谓的空闲,而是被直接销毁。
当这个参数被设置为60的时候,一个线程完成任务后,将进入60秒的空闲状态,这个时间以内,如果线程池指派这个线程任务,它会从空闲状态进入工作状态,如此往复。但是如果,60秒的时间内都没有新的任务被指派给这个空闲线程,那么它就会被真正的销毁。
这个机制可以帮助线程复用。
其次,有一个参数对线程池的能力有至关重要的作用:
BlockingQueue<Runnable> workQueue //在执行任务之前用于保留任务的队列
通过这个参数,注入一个实行不同策略的阻塞队列。
什么是阻塞队列?
在队列为空时,获取元素的线程会等待队列变为非空。而当队列满时,存储元素的线程会等待队列可用。
联想一下生产者-消费者机制就能理解阻塞队列的作用。
下面来看看,有哪些阻塞队列,它们都被用在了哪些线程池上,实现了什么样的期望:
-
LinkedBlockingQueue 链表阻塞队列,按FIFO排序,
newFixedThreadPool线程池使用了这个队列:
在构造的时候,创建了一个无界的LinkedBlockingQueue,故线程池可以放入无限数量的任务,等着一个一个被消费。
newSingleThreadExecutor线程池也用了这个队列:
构造时创建了一个无界的LinkedBlockingQueue,线程池可以一直放入任务,没有限制,只不过能运行的线程只有一个而已。
-
SynchronousQueue 无元素阻塞队列,每个插入必须等到另一个线程移除。用“配对”来形容会更合适,当一个线程进行插入的时候,必须有一个移除线程和它配对,拿走它插入的数据,否则就会一直阻塞。
newCachedThreadPool线程池使用了这个队列:
这个线程池核心的线程数量为0,而最大的线程数量为无穷,而有60s的空闲延迟时间,所以当一个任务进入时,必须要找到一条工作线程处理他,如果当前没有空闲的线程,那么就会再创建一条新的线程(非核心线程)。感觉有点像是没有线程池一样,但是有一个好处就是,线程不像是“野生”创建的那样执行完后就销毁,而是会有被保留的机会,被重用。而SynchronousQueue的作用只不过是在于让所有请求都得到回复。
-
DelayQueue 延迟队列:支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素。在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。
newScheduledThreadPool线程池使用了这个队列:
容易理解,这个队列可以满足延迟的需求。
线程池的执行:
线程池的执行,不论是否为通过上面所述的方法创建的,还是手动使用构造方法创建的,都遵循以下的运行规则:
- 因为线程池区分核心线程,故一个任务被提交后,如果核心数量还没满,就创建核心线程并执行任务。
- 核心线程已满,如果线程池中的队列(不管是哪种队列)没有满,那就让任务进入队列。
- 如果队列也满了,那线程池会检查“是否当前整个线程池的线程数量已经超过了线程池的最大值”(线程池中的线程数量最大值 int maximumPoolSize),如果没有超过了话,线程池就立即创建一个非核心的线程并执行任务。
- 如果很不幸,以上几个条件都依次没有满足,那只能采取拒绝策略(RejectPolicy)了。
线程池的四种拒绝策略
- AbortPolicy(直接抛出一个异常)
- DiscardPolicy(丢弃任务,不做处理)
- DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池)
- CallerRunsPolicy (直接由调用线程池的线程来显式执行了)
状态转换
线程池的状态:
-
RUNNING 运行中,平常处理。
-
SHUTDOWN 不接受新的任务 但继续处理队列的任务。
当队列为空,并且线程池中执行的任务也为空,就会进入TIDYING状态。
-
STOP 不接受新任务 不处理队列任务 甚至切断正在执行任务的线程。
当线程池里的任务为空,就会进入TIDYING状态。
-
TIDYING 任务为空了,会执行terminated方法。
-
TERMINATED 执行了terminated方法后,会进入这个状态,线程池终结。