线程池的好处
第一 降低资源消耗。通过重复利用已经创建成功的线程降低线程创建和销毁时造成的资源消耗。
第二 提高响应速度。 当任务到达的时候不需要等到线程创建就能立刻执行。
第三 提高线程可管理性。线程是稀缺资源,如果无限创建会造成系统资源,降低系统的稳定性,所以使用线程池进行统一的分配,调优和监控。
1.基本原理
当向线程中提交一个任务时,线程池的处理步骤如下
1)线程池判断核心线程池中的线程是否都在执行任务,如果不是则创建一个新的线程来执行当前的任务。如果核心线程池中的线程都在执行任务,进行下一个流程。
2)线程池判断工作队列是否已经满了,如果工作队列没有满,就将任务存储到这个工作队列中去,如果工作队列已经满了,进行下一个流程。
3)线程池判断线程池中的线程是否都处于工作状态,如果没有则创建一个新的线程来执行当前任务,如果线程都处于工作状态则交给饱和策略来处理这个任务。
ThreadPoolExecutor线程池执行execute方法分为下面四种情况
1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(执行这一步骤需要获取全局锁)
2)如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
3)如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(执行这一步骤需要获取全局锁)
4)如果创建新线程将使当前运行的线程超出maxiMumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。
ThreadPoolExecutor采用以上的设计思路,在完成预热(当前线程大于或等于corePoolSize)的时候避免过多的获取全局锁,几乎所有的方法调用都是走的第二个步骤。
2.源码分析
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 如果线程数小于基本线程数,则创建线程并执行当前任务
if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
// 如果线程数大于或等于基本线程数或者线程创建失败,则将当前任务放入到工作队列中
if (runState == RUNNING && workQueue.offer(command)) {
if (runState != RUNNING || poolSize == 0)
ensureQueuedTaskHandled(command);
}
//如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,则创建一个线程执行任务
else if (!addIfUnderMaximumPoolSize(command))
// 抛出RejectedExecutionException异常
reject(command); // is shutdown or saturated
}
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
工作线程
线程池创建线程的时,会将线程封装成工作线程Worker,worker在执行完任务后,还会循环获取工作队列中的任务来执行
worker.run()
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
3.线程池的使用
使用ThreadPoolExecutor来创建一个线程池
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
milliseconds,runnableTaskQueue, handler);
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
创建线程池需要输入几个参数
1)corePoolSize(线程池的基本大小)
2)runnableTaskQueue(任务阻塞队列)
3)maximumPoolSize(线程池最大数量)
4)ThreadFactory(创建线程的工厂)
5)RejectidExecutionHandler(饱和策略)
.AbortPolicy:直接抛出异常
.CallerRunsPolicy:只用调用者所在线程来运行任务
.DiscardOldestPolicy:丢弃队列里面最近的一个任务,并执行当前任务
.DiscardPolicy:直接丢弃不做处理
6)keepAliveTime 线程空闲时存活时间
7)TImeUnit 线程存活时间单位
4.向线程池中提交任务
execute和submit两个方法
execute用于提交不需要返回值的任务,所以无法判断任务是否已经执行成功。
submit用于提交需要返回值的任务,线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功。并且通过future.get方法可以获得返回值。
5.关闭线程池
线程池提供了两个方法,一个是ShutDown方法另一个是ShutDownNow方法
他们的原理都是遍历线程池中的线程,统一调用他们的interrupt方法来中断线程,所以无法响应中断的线程没法停止。
shutDown和shutDownNow方法之间存在一定区别,
shutDown会将线程池的状态设置成shutdown状态,然后中断所有没有正在执行任务的线程。
shutDownNow会直接将线程的状态设置成stop状态,然后中断所有线程,并不会等待正在执行任务的线程执行完毕。
一般选择shutDown来关闭线程池,当然如果线程不需要执行完,也可以选择shutDownNow来关闭线程池。
问题
1.execute()和submit()区别2.shutDown和shutDownNow区别3.线程池的基本原理4.三个线程池阻塞队列的区别和选择。ArrayBlockingQueue是有界的阻塞队列,生产者和消费者使用的是一把lock,当生产者调用put方法时和消费者调用take方法时使用的是同一个lock。LinkedBlockingQueue是无界的阻塞队列,生产者和消费者使用的是两把lock,当生产者调用put方法时使用的时putLock,消费者调用take方法时使用的是takeLock。SynchronousQueue是优先级的无界阻塞队列,他的put和take必须是成对存在的否则put方法是没办法进行的。 整篇博客来自《Java并发编程的艺术》