首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java 线程池原理与使用

Java 线程池原理与使用

作者头像
Tim在路上
发布2020-08-04 21:43:00
4390
发布2020-08-04 21:43:00
举报

在java 中我们会一般要求创建线程必须使用线程池,因为这样可以避免资源消耗,通过重复利用已经创建的线程来降低线程创建和销毁所造成的消耗, 其次当任务到达时任务可以不用等到线程创建就立即执行,最后可以提高线程的管理性,管控线程,不让线程无限制创建.

线程池的创建

我们可以使用 ThreadPoolExecutor 创建线程池

这里我们不适用Executors 来创建线程池,是因为,Executors 使用默认参数不用自定义参数,

自己经常使用Executors提供的工厂方法创建线程池,所以忽略了线程池内部的实现,同时可以规避规避资源耗尽的风险.

new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
milliseconds,runnableTaskQueue, handler);
  1. corePoolSize(线程池的基本大小): 当提交一个任务到线程池时,线程 池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务 也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。 如果调用了线程池的 prestartAllCoreThreads 方法,线程池会提前创建 并启动所有基本线程。
  2. runnableTaskQueue (任务队列):用于保存等待执行的任务的阻塞队列。 可以选择以下几个阻塞队列。

ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队 列按 FIFO(先进先出)原则对元素进行排序。

LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按 FIFO (先进先出) 排序元素,吞吐量通常要高于 ArrayBlockingQueue。静态工厂方法 Executors.newFixedThreadPool()使用了这个队列。

SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必 须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 吞吐量通常要高于 LinkedBlockingQueue,静态工厂方法 Executors.newCachedThreadPool 使用了这个队列。

PriorityBlockingQueue:一个具有优先级的无限阻塞队列。

  1. maximumPoolSize(线程池最大大小):线程池允许创建的最大线程数。 如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建 新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就 没什么效果。
  2. ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创 建出来的线程设置更有意义的名字。
  3. RejectedExecutionHandler(饱和策略): 当队列和线程池都满了,默认 AbortPolicy:直接抛出异常, 还可设置 CallerRunsPolicy:只用调用者所在线程来运行任务。 o DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前 任务。 o DiscardPolicy:不处理,丢弃掉。 o 当然也可以根据应用场景需要来实现 RejectedExecutionHandler 接口自定义策略。如记录日志或持久化不能处理的任务。

向线程池提交任务

我们可以使用 execute 提交的任务,但是 execute 方法没有返回值,所以无法判 断任务是否被线程池执行成功。通过以下代码可知 execute 方法输入的任务是一 个 Runnable 类的实例。

threadsPool.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
}
});

可以使用 submit 方法来提交任务,它会返回一个 future,那么我们可以 通过这个 future 来判断任务是否执行成功,通过 future 的 get 方法来获取返回 值, get 方法会阻塞住直到任务完成,而使用 get(long timeout, TimeUnit unit) 方法则会阻塞一段时间后立即返回,这时有可能任务没有执行完。

Future<Object> future = executor.submit(harReturnValuetask);
try {
Object s = future.get();
} catch (InterruptedException e) {
// 处理中断异常
} catch (ExecutionException e) {
// 处理无法执行任务异常
2} finally {
// 关闭线程池
executor.shutdown();
}

线程池的关闭

调用线程池的 shutdown 或 shutdownNow 方法来关闭线程池, 它们 的原理是遍历线程池中的工作线程,然后逐个调用线程的 interrupt 方法来中断 线程,所以无法响应中断的任务可能永远无法终止。

shutdown 和 shutdownNow 的区别

shutdownNow 首先将线程池的状态设置成 STOP,然后尝试停止所有的正在执行或 暂停任务的线程,并返回等待执行任务的列表,而 shutdown 只是将线程池的状 态设置成 SHUTDOWN 状态,然后中断所有没有正在执行任务的线程。

通常调用 shutdown 来关闭线程池,表示任务执行完关闭,如果任务不一定要执行完, 则可以调用 shutdownNow。

线程池执行过程

提交一个任务到线程池,首先判断基本线程池是否已满,没有满不管线程池中有没有空闲线程都创建一个新线程来执行任务, 判断工作队列是否已满,没满则将新提交的任务存储在工作队列, 满了, 判断整个线程池是否已满, 没满, 则创建一个新的工作线程来 执行任务,满了,则交给饱和策略来处理这个任务.

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 在执 行完任务后,还会无限循环获取工作队列里的任务来执行。

public void run() {
try {
Runnable task = firstTask;
firstTask = null;
while (task != null || (task = getTask()) != null) {
runTask(task);
task = null;
}
} finally {
workerDone(this);
}
}

合理的配置线程池

任务的性质:CPU 密集型任务,IO 密集型任务和混合型任务。 任务的优先级:高,中和低。 任务的执行时间:长,中和短。 任务的依赖性:是否依赖其他系统资源,如数据库连接。

  1. CPU 密集型任务, 配置尽 可能小的线程,如配置 Ncpu+1 个线程的线程池。
  2. IO 密集型任务则由于线程并不 是一直在执行任务,则配置尽可能多的线程,如 2*Ncpu。
  3. 混合型的任务,如果 可以拆分,则将其拆分成一个 CPU 密集型任务和一个 IO 密集型任务,只要这两 个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞 吐率,如果这两个任务执行时间相差太大,则没必要进行分解。
  4. 优先级不同的任务可以使用优先级队列 PriorityBlockingQueue 来处理。它可以 让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到 队列里,那么优先级低的任务可能永远不能执行。
  5. 线程提交 SQL 建议使用有界队列,有界队列能增加系统的稳定性和预警能力,可以根据需要设 大一点,比如几千.

线程池的监控

通过继承线程池并重写线程池的 beforeExecute, afterExecute 和 terminated 方法,我们可以在任务执行前,执行后和线程池关 闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 线程池的创建
  • 向线程池提交任务
  • 线程池的关闭
  • 线程池执行过程
  • 合理的配置线程池
  • 线程池的监控
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档