万字总结最全Java线程池ThreadPoolExecutor面试题(四)

简介: 万字总结最全Java线程池ThreadPoolExecutor面试题(四)

5 创建线程池

ThreadPoolExecutor 自定义线程池

1.png

它们都是某种线程池,可以控制线程创建、释放,并通过某种策略尝试复用线程去执行任务的一个管理框架。因此最终所有线程池的构造函数都调用了Java5后推出的ThreadPoolExecutor的如下构造器:

image.png

第1个参数: corePoolSize 表示常驻核心线程数

如果等于0,则任务执行完之后,没有任何请求进入时销毁线程池的线程;

如果大于0,即使本地任务执行完毕,核心线程也不会被销毁.

这个值的设置非常关键;

设置过大会浪费资源;

设置过小会导致线程频繁地创建或销毁.


第2个参数: maximumPoolSize 表示线程池能够容纳同时执行的最大线程数

从第1处来看,必须>=1.

如果待执行的线程数大于此值,需要借助第5个参数的帮助,缓存在队列中.

如果maximumPoolSize = corePoolSize,即是固定大小线程池.


第3个参数: keepAliveTime 表示线程池中的线程空闲时间

当空闲时间达到keepAliveTime时,线程会被销毁,直到只剩下corePoolSize个线程;

避免浪费内存和句柄资源.

在默认情况下,当线程池的线程数大于corePoolSize时,keepAliveTime才起作用.

但是当ThreadPoolExecutor的allowCoreThreadTimeOut = true时,核心线程超时后也会被回收.


第4个参数: TimeUnit表示时间单位

keepAliveTime的时间单位通常是TimeUnit.SECONDS.


第5个参数: workQueue 表示缓存队列

当请求的线程数大于maximumPoolSize时,线程进入BlockingQueue.

后续示例代码中使用的LinkedBlockingQueue是单向链表,使用锁来控制入队和出队的原子性;

两个锁分别控制元素的添加和获取,是一个生产消费模型队列.


第6个参数: threadFactory 表示线程工厂

它用来生产一组相同任务的线程;

线程池的命名是通过给这个factory增加组名前缀来实现的.

在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的.


第7个参数: handler 表示执行拒绝策略的对象

当超过第5个参数workQueue的任务缓存区上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护.

友好的拒绝策略可以是如下三种:

(1 ) 保存到数据库进行削峰填谷;在空闲时再提取出来执行

(2)转向某个提示页面

(3)打印日志

2.1.1 corePoolSize(核心线程数量)

线程池中应该保持的主要线程的数量,即使线程处于空闲状态。

当提交一个任务到线程池时,若线程数量<corePoolSize,线程池会创建一个新线程放入works(一个HashSet)中执行任务,即使其他空闲的基本线程能够执行新任务也还是会创建新线程

等到需要执行的任务数大于线程池基本大小时就不再创建,会尝试放入等待队列workQueue。

prestartAllCoreThreads

调用线程池的prestartAllCoreThreads(),线程池会提前创建并启动所有核心线程,使它们空闲地等待工作。 这将覆盖仅在执行新任务时才启动核心线程的默认策略


image.png

除非设置了

allowCoreThreadTimeOut

若为false(默认),则即使处于空闲状态,核心线程也保持活跃状态。

若为true,则核心线程使用keepAliveTime来超时等待工作,线程池在空闲时同样回收核心线程。

image.png

2.1.2 maximumPoolSize(线程池最大线程数)

线程池允许创建的最大线程数

若队列满,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程放入works中执行任务,CashedThreadPool的关键,固定线程数的线程池无效

若使用了无界任务队列,这个参数就没什么效果


workQueue

存储待执行任务的阻塞队列,这些任务必须是Runnable的对象(如果是Callable对象,会在submit内部转换为Runnable对象)


runnableTaskQueue(任务队列):用于保存等待执行的任务的阻塞队列.可以选择以下几个阻塞队列.


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

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

ThreadFactory:用于设置创建线程的工厂,可以通过线程工厂给每个创建出来的线程设置更有意义的名字.使用开源框架guava提供ThreadFactoryBuilder可以快速给线程池里的线程设置有意义的名字,代码如下


new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build();

RejectedExecutionHandler(拒绝策略)

当队列和线程池都满,说明线程池饱和,必须采取一种策略处理提交的新任务

策略默认AbortPolicy,表无法处理新任务时抛出异常

在JDK 1.5中Java线程池框架提供了以下4种策略

AbortPolicy:丢弃任务,抛出 RejectedExecutionException

CallerRunsPolicy:只用调用者所在线程来运行任务,有反馈机制,使任务提交的速度变慢)。

DiscardOldestPolicy

若没有发生shutdown,尝试丢弃队列里最近的一个任务,并执行当前任务, 丢弃任务缓存队列中最老的任务,并且尝试重新提交新的任务

DiscardPolicy:不处理,丢弃掉, 拒绝执行,不抛异常

当然,也可以根据应用场景需要来实现RejectedExecutionHandler接口自定义策略.如记录日志或持久化存储不能处理的任务


    final void reject(Runnable command) {
        // 执行拒绝策略
        handler.rejectedExecution(command, this);
    }

handler 构造线程池时候就传的参数,RejectedExecutionHandler的实例

RejectedExecutionHandlerThreadPoolExecutor 中有四个实现类可供我们直接使用,当然,也可以实现自己的策略,一般也没必要。

    //只要线程池没有被关闭,由提交任务的线程自己来执行这个任务
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        public CallerRunsPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }
    // 不管怎样,直接抛出 RejectedExecutionException 异常
    // 默认的策略,如果我们构造线程池的时候不传相应的 handler ,则指定使用这个
    public static class AbortPolicy implements RejectedExecutionHandler {
        public AbortPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }
    // 不做任何处理,直接忽略掉这个任务
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }
    // 若线程池未被关闭
    // 把队列队头的任务(也就是等待了最长时间的)直接扔掉,然后提交这个任务到等待队列中
    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        public DiscardOldestPolicy() { }
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

keepAliveTime(线程活动保持时间)

线程没有任务执行时最多保持多久时间终止

线程池的工作线程空闲后,保持存活的时间。

所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率


TimeUnit(线程活动保持时间的单位):指示第三个参数的时间单位;可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒(NANOSECONDS,千分之一微秒)


从代码第2处来看,队列、线程工厂、拒绝处理服务都必须有实例对象;

但在实际编程中,很少有程序员对这三者进行实例化,而通过Executors这个线程池静态工厂提供默认实现

目录
相关文章
|
2天前
|
存储 安全 Java
[Java基础面试题] Map 接口相关
[Java基础面试题] Map 接口相关
|
2天前
|
Java
[Java 面试题] ArrayList篇
[Java 面试题] ArrayList篇
|
2天前
|
Java 调度
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
Java面试必考题之线程的生命周期,结合源码,透彻讲解!
28 1
|
2天前
|
存储 安全 Java
每日一道Java面试题:说一说Java中的泛型?
今天的每日一道Java面试题聊的是Java中的泛型,泛型在面试的时候偶尔会被提及,频率不是特别高,但在日后的开发工作中,却是是个高频词汇,因此,我们有必要去认真的学习它。
15 0
|
2天前
|
Java 编译器
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
每日一道Java面试题:方法重载与方法重写,这把指定让你明明白白!
14 0
|
3天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
7天前
|
XML 缓存 Java
Java大厂面试题
Java大厂面试题
18 0
|
7天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
14 0
|
7天前
|
存储 安全 Java
Java大厂面试题
Java大厂面试题
14 0
|
8天前
|
安全 Java
就只说 3 个 Java 面试题 —— 02
就只说 3 个 Java 面试题 —— 02
19 0