面试官:你知道创建线程的方式有哪些吗?
派大星:主要有两种方式,
- 一种是通过 Executors 创建,
- 另一种是通过 ThreadPoolExecutorPools 创建。
面试官:很好。你能说一下这些创建方式的优缺点吗?
派大星:当然。但是我要先提醒一下,不建议使用 Executors 创建线程。
FixedThreadPool
和SingleThreadPool
允许的请求队列长度为Integer.MAX_VALUE
,从而可能会堆积大量请求,造成 OOM;CachedThreadPool
和ScheduledThreadPool
允许的创建线程数量为Integer.MAX_VALUE
,可能会创建大量的线程,从而导致 OOM。除此之外,Executors 创建的线程池无法自定义配置,不够灵活。建议使用ThreadPoolExecutorPools
创建方式进行自定义配置
面试官:你能介绍一下 Executors 吗?
派大星:Executors 是一个工具类,提供了创建线程池的方法。它提供了 6 种创建线程池的方式,包括 newFixedThreadPool、newCachedThreadPool、newSingleThreadExecutor、newScheduledThreadPool 等。
面试官:那你能介绍一下 newFixedThreadPool 的使用方法吗?
派大星:当然可以。newFixedThreadPool 可以创建一个固定大小的线程池,可控制并发的线程数,超出的线程会在队列中等待。我们可以通过以下代码来创建一个固定大小的线程池:
代码语言:javascript复制ExecutorService threadPool = Executors.newFixedThreadPool(2);
面试官:那你能介绍一下 Executors 提供的其他创建方式吗?
派大星:当然可以。除了 newFixedThreadPool,Executors 还提供了其他 5 种创建方式,分别是 newCachedThreadPool
、newSingleThreadExecutor
、newScheduledThreadPool
、newSingleThreadScheduledExecutor
和 newWorkStealingPool
。
- newCachedThreadPool 可以创建一个可以缓存的线程池,若线程数超过处理所需,则会缓存一段时间后回收。若线程数不够,则新建线程。
- newSingleThreadExecutor 可以创建单个线程数的线程池,它可以保证先进先出的执行顺序。
- newScheduledThreadPool 可以创建一个可以执行延迟任务的线程池。
- newSingleThreadScheduledExecutor 可以创建一个单线程的可以执行延迟任务的线程池。
- newWorkStealingPool 可以创建一个抢占式执行的线程池(任务执行顺序不确定),注意此方法只有在 JDK 1.8 版本中才能使用。
面试官:这些创建方式有什么优缺点呢?
派大星:这些创建方式各有优缺点。例如
- newCachedThreadPool 可以根据任务数量自动调整线程池的大小,但是如果任务数量过多,会导致线程数过多,从而导致系统资源不足。
- 而 newFixedThreadPool 可以限制线程的数量,避免线程数过多,但是如果任务数量过多,会导致任务在队列中等待,从而导致响应时间变慢。
面试官:那你建议我们使用哪种创建方式呢?
派大星:我建议使用 ThreadPoolExecutorPools 创建方式进行自定义配置。因为 Executors 创建方式的缺点是无法进行自定义配置,而 ThreadPoolExecutorPools 可以通过自定义配置来满足不同的需求。
面试官:你知道线程池的参数有哪些吗?
派大星:一共有七个参数。
- 第一个是核心线程数(corePoolSize),表示线程池中始终存在的线程数;
- 第二个是最大线程数(maximumPoolSize),表示线程池中允许的最大线程数,当线程池中的任务队列满了之后可以创建的最大线程数;
- 第三个是最大线程数可以存活的时间(maximumPoolSize),当线程中没有任务执行时,最大线程就会销毁一部分,最终保持核心线程数量的线程;
- 第四个是时间单位(unit),用于设定线程的存活时间,有七种可以选择,包括天、小时、分、秒、毫秒、微妙和纳秒;
- 第五个是一个阻塞队列(unit),用来存储线程池等待执行的任务,包含七种类型
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。
- SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保持它们。
- PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。
- DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能从中提取元素。
- LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似,还含有非阻塞方法。
- LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
- 第六个是线程工厂(threadFactory),主要用来创建线程,默认为正常优先级,非守护线程;
- 第七个是拒绝策略(handler),用于拒绝处理任务时的策略,系统有四种可选。默认的策略为抛出异常。
- AbortPolicy:拒绝并抛出异常
- CallerRunsPolicy:使用当前调用的线程来执行任务
- DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务,并执行当前任务
- DiscardPolicy:忽略并抛弃当前任务
所以综上情况所述,我们推荐使用 ThreadPoolExecutor 的方式进行线程池的创建,因为这种创建方式更可控,并且更加明确了线程池的运行规则,可以规避一些未知的风险。