线程池超详解(原理流程参数及创建使用)

线程池超详解(原理流程参数及创建使用)-mikechen

线程池是Java高级工程师必备技术,下面我详细介绍线程的原理参数、执行流程、以及详细的创建方式@mikechen

线程池简介

线程 thread是操作系统能够进行运算调度的最小单位,它被包含在进程之中,我们的程序最终都是由线程进行运作。

线程池超详解(原理流程参数及创建使用)-mikechen

在Java中创建和销毁线程的动作是很消耗资源的,因此就出现了所谓“池化资源”技术,线程这里就出现了“线程池”,线程池是池化资源技术的一个应用。

所谓线程池,顾名思义就是预先按某个规定创建若干个可执行线程放入一个容器中(线程池),需要使用的时候从线程池中去取,用完之后不销毁而是放回去,从而减少了线程创建和销毁的次数,达到节约资源的目的。

 

为什么需要线程池?

主要分为如下3点:

1.降低资源消耗

刚刚上面提到的池化技术,这是线程池的第一个核心要素,利用池化技术可以减少了线程创建和销毁的次数,从而降低降低资源消耗。

2.提高响应速度

每当有任务到来时,直接复用线程池中的线程,而不需要等待新线程的创建,这个动作可以带来响应速度的提升。

3.提升系统稳定

可以根据系统的承受能力,调整线程池中的工作线程的数量,防止因为线程过多服务器变慢或死机,从而提升系统的稳定性。

 

线程池的核心参数

所谓的线程池的 参数是指在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数。

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
}

如下图所示:

线程池超详解(原理流程参数及创建使用)-mikechen

1.corePoolSize(线程池的基本大小)

当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建。如果调用了线程池的prestartAllCoreThreads方法,线程池会提前创建并启动所有基本线程。

2.runnableTaskQueue(任务队列)

用于保存等待执行的任务的阻塞队列。可以选择以下几个阻塞队列。

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
  • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。
  • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue。
  • PriorityBlockingQueue:一个具有优先级得无限阻塞队列。

3.maximumPoolSize(线程池最大大小)

线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是如果使用了无界的任务队列这个参数就没什么效果。

4.ThreadFactory:用于设置创建线程的工厂

可以通过线程工厂给每个创建出来的线程设置更有意义的名字,Debug和定位问题时非常又帮助。

5.RejectedExecutionHandler(饱和策略)

当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务。这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。以下是JDK1.5提供的四种策略。n AbortPolicy:直接抛出异常。

  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。
  • 当然也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略。如记录日志或持久化不能处理的任务。

6.keepAliveTime(线程活动保持时间)

线程池的工作线程空闲后,保持存活的时间。所以如果任务很多,并且每个任务执行的时间比较短,可以调大这个时间,提高线程的利用率。

7.TimeUnit(线程活动保持时间的单位)

可选的单位有天(DAYS),小时(HOURS),分钟(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。

 

线程池的执行流程

1、判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建)则创建一个新的工作线程来执行任务。如果核心线程都在执行任务,则进入下个流程。

2、线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

3、判断线程池里的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

线程池超详解(原理流程参数及创建使用)-mikechen

 

线程池的创建方式

Java提供的7种线程池实现,这 7 种实现方法分别是:

1)Executors.newFixedThreadPool

创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。

2)Executors.newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

3)Executors.newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

4)Executors.newScheduledThreadPool

创建一个可以执行延迟任务的线程池。

5)Executors.newSingleThreadScheduledExecutor

创建一个单线程的可以执行延迟任务的线程池。

6)Executors.newWorkStealingPool

创建一个抢占式执行的线程池(任务执行顺序不确定),这个是JDK 1.8 添加。

7)ThreadPoolExecutor

手动创建线程池的方式,它创建时最多可以设置 7 个参数。

 

陈睿mikechen

10年+大厂架构经验,资深技术专家,就职于阿里巴巴、淘宝、百度等一线互联网大厂。

关注「mikechen」公众号,获取更多技术干货!

后台回复面试即可获取《史上最全阿里Java面试题总结》,后台回复架构,即可获取《阿里架构师进阶专题全部合集

评论交流
    说说你的看法