AI智能
改变未来

浅析Java中的线程池


Java中的线程池

几乎所有需要异步或并发执行任务的程序都可以使用线程池,开发过程中合理使用线程池能够带来以下三个好处:

  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性

1. 线程池的实现原理

当我们提交一个新任务到线程池时,线程池的处理流程如下:

其中,任何创建新线程的操作都需要获取全局锁。

ThreadPoolExecutor

采取上述步骤的设计思路,是为了在执行

execute()

方法时,尽可能地避免获取全局锁。在

ThreadPoolExecutor

完成预热后,几乎所有的

execute()

方法都是执行步骤二,而步骤二不需要获取全局锁。

工作线程在完成当前的任务后,会自己到工作队列中循环获取任务来执行:

2. 线程池的使用

2.1 创建线程池

我们通过

ThreadPoolExecutor

来创建线程池。

new ThreadPoolExecutor(corePoolSize // 核心线程池大小,maximumPoolSize // 线程池大小,keepAliveTime // 最长存活时间,milliseconds // 时间单位,runnableTaskQueue // 工作队列,handler) // 饱和策略
  • corePoolSize 参数为线程池基本大小,即核心线程池的最大容量。当核心线程池未满时,只要收到新任务都会创建一个新的线程。当核心线程池满且无空闲工作线程时,会将任务存到任务队列中。

  • **maximumPoolSize **参数为线程池最大数量,即线程池允许创建的最大线程数。如果任务队列满了,将会创建新线程来执行任务,直到线程数量达到该最大数量。值得注意的是如果使用了无界的任务队列,那么这个参数就没有效果。

  • keepAliveTime 参数为空闲线程存活时间。

  • **TimeUnit **参数为线程活动保持时间的单位。

  • **workQueue **参数为任务队列,它是一个用于保存等待执行的任务的阻塞队列,可以选择以下几个。

    队列 底层 是否有界 其他
    ArrayBlockingQueue 数组· 有界 FIFO
    LinkedBlockingQueue 链表 无界 FIFO
    SynchronousQueue 不存储元素 有界 每个插入操作都必须等到另一个线程调用移除操作
    PriorityBlockingQueue 数组 无界 有优先级
  • ThreadFactor参数为线程工厂。可以通过线程工厂为每个创建出来的线程设置更有意义的名字。

  • RejectedExecutionHandler参数为饱和策略。当队列和线程池都满了,我们需要使用饱和策略来处理新任务。一般有以下四种策略。

      AbortPolicy

      :直接抛出异常。

    1. CallerRunsPolicy

      :只用调用者所在线程来运行任务。

    2. DiscardOldestPolicy

      :丢弃队列里最近的一个任务,并执行当前任务。

    3. DiscardPolicy

      :不处理,丢掉。

    4. 当然,也可以通过
      RejectedExecutionHandler

      接口自定义策略,比如记录日志或者持久化存储不能处理的任务。

为什么要求用 ThreadPoolExecutor 创建线程池

否则有资源耗尽的风险,

Executors

返回的线程池对象弊端如下:

  • FixedThreadPool

    SingleThreadPool

    :允许的请求队列长度为

    Integer.MAX_VALUE

    ,可能会堆积大量请求,导致 OOM。

  • CachedThreadPool

    ScheduledThreadPool

    :允许的创建线程数量为

    Integer.MAx_VALUE

    ,可能会创建大量线程,导致 OOM。

2.2 向线程池提交任务

可以用

execute()

submit()

两个方法向线程池提交任务,前者提交不需要返回值的任务,后者提交需要返回值的任务。

2.3 关闭线程池

使用

shutdown()

shutdownNow()

方法来关闭线程池。原理是遍历线程池中的工作线程,逐个调用

interrupt()

方法。

shutdownNow()

会将线程池状态设置为

STOP

,并尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表。而

shutdown()

只是将线程池状态设置为

SHUTDOWN

状态,然后中断所有没有正在执行任务的线程。

通常调用

shutdown()

方法,如果任务不一定要执行完,就调用

shutdownNow()

3. 合理配置线程池

我们需要通过任务的特性,来分析合理的线程池配置方式:

  • 任务的性质:CPU 密集型任务、IO 密集型任务和混合型任务CPU 密集型任务配置尽可能少的线程,如配置 CPU 核数+ 1 个线程。
  • IO 密集型人物应配置尽可能多的线程,如配置 2 * cpu 核数个线程。
  • 混合型任务尽量拆分
  • 任务优先级:高、中、低
      优先级不同的任务可以使用优先级队列

      PriorityBlockingQueue

      来处理。

  • 任务执行时间:长、中、短
      执行时间不同的任务交给不同规模的线程池处理。
  • 任务依赖性:是否以来其他系统资源,如数据库资源
      依赖数据库连接池的任务,因为线程提交 SQL 后需要等待数据库返回结果,等待时间越长则 CPU 空闲时间越长,那么线程数应该设置得越大,才能更好地使用 CPU。
  • 赞(0) 打赏
    未经允许不得转载:爱站程序员基地 » 浅析Java中的线程池