vlambda博客
学习文章列表

Java线程池七个参数详解

一、前言

1.1 线程池是什么

线程池(thread pool):线程池是一种池化技术,类似的有数据库连接池,HTTP连接池、IP池等。

池化管理线程,无需额外创建和销毁线程,能避免创建过多线程导致线程频繁调度从而减低性能

1.2 线程池有什么用

总的来说有4点好处:

  • 降低资源消耗:通过重复利用现有的线程来执行任务,避免多次创建和销毁线程。
  • 提高相应速度:因为省去了创建线程这个步骤,所以在任务来的时候,可以立刻开始执行。
  • 提高线程的可管理性:线程池进行统一的分配、调优和监控。
  • 提供更多更强大的功能:线程池的可拓展性使得我们可以自己加入新的功能,比如说定时、延时来执行某些线程。

二、线程池的设计与实现

2.1 总体设计

Java中线程池核心实现类是ThreadPoolExecutor,首先看下ThreadPoolExecutor的UML类图,了解ThreadPoolExecutor的继承关系。


ThreadPoolExecutor的顶层接口是Executor:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需要提供Runnable对象,将任务运行逻辑提交到执行器(Executor)中,由Executor框架来调配和执行。

2.2 ThreadPoolExecutor的运行逻辑

当任务提交到线程池里之后,需要经过以下流程


这里设corePoolSize(核心线程数量)为10,maximumPoolSize (最大线程数量)为20

三、线程池的7个核心参数

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

3.1 corePoolSize 线程池核心线程大小

线程池中维护的一个最少的线程数量,即使这些线程处于空闲状态,他们也不会被销毁,除非设置了allowCoreThreadTimeOut。

3.2 maximumPoolSize 线程池最大线程数量

一个任务被提交到线程池之后,首先会到工作队列中,如果工作队列满了,则会创建一个新的线程,然后从工作队列中取出一个任务交给新线程处理,而将刚提交上来的任务放入到工作队列中。线程池最大的线程数量由maximunPoolSize来指定。

3.3 keepAliveTime 空闲线程存活时间

一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定的时间后,这个空闲的线程将被销毁,这个指定的时间就是keepAliveTime。

3.4 unit 空闲线程存活时间单位

keepAliveTime的计量单位,是一个枚举java.util.concurrent.TimeUnit。

3.5 workQueue 工作队列

新任务被提交之后,会先进入到此工作队列中,任务调度时再从队列中取出任务。jdk一共提供了四种工作队列。

  • ArrayBlockingQueue 数组型阻塞队列:数组结构,初始化时传入大小,有界,FIFO(先进先出),使用一个重入锁,默认使用非公平锁,入队和出队共用一个锁,互斥。
  • LinkedBlockingQueue 链表型阻塞队列:链表结构,默认初始化大小为Integer.MAX_VALUE,有界(近似无解),FIFO,使用两个重入锁分别控制元素的入队和出队,用Condition进行线程间的唤醒和等待。
  • SynchronousQueue 同步队列:容量为0,添加任务必须等待取出任务,这个队列相当于通道,不存储元素。
  • PriorityBlockingQueue 优先阻塞队列:无界,默认采用元素自然顺序升序排列。
  • DelayQueue 延时队列:无界,元素有过期时间,过期的元素才能被取出。

3.6 threadFactory 线程工厂

创建新线程的时候使用的工厂,可以用来指定线程名,是否为daemon线程等等。

3.7 handler 拒绝策略

当工作队列中的任务已经达到了最大的限制,并且线程池中线程数量达到了最大限制,如果这时候有新任务进来,就会采取拒绝策略,jdk中提供了四种拒绝策略。

  • AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • DiscardPolicy:丢弃任务,但是不抛出异常。可能导致无法发现系统的异常状态。
  • DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务。
  • CallerRunsPolicy:由调用线程处理该任务。

四、Executors创建线程池

java.util.concurrent.Executors提供了静态方法创建线程池。

  • newCachedThreadPool :创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,否则新建线程。(线程最大并发数不可控制)
  • newFixedThreadPool:创建一个固定大小的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool :创建一个定时线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor :创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

弊端:

  1. newFixedThreadPool和newSingleThreadExecutor:主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
  2. newCachedThreadPool和newScheduledThreadPool:主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。

五、总结

我们了解了什么是线程池,线程池有什么用,怎样创建线程池,线程池创建线程执行任务的逻辑。