通过生活案例快速 Get 线程池七个参数和工作原理

简介: 通过生活案例快速 Get 线程池七个参数和工作原理


祝 我们 亲爱的祖国生日快乐,祖国在一天天的变强大,我们也要加油哦,我爱你,中国!!!

如果信仰有颜色,我想那一定是红色。

也祝 xdm 国庆快乐 万事胜意!!!

这次用一个生活案例来让你快速的 get 到线程池的工作原理和七个参数的作用。

最近在复习面试题,巩固一下自己。学完看看面试题,可以很有效的知道的不足。也是为以后做准备。

你好,我是博主宁在春

希望文章能够让你有所收获,也让我们也一起努力!!!

本文主要针对线程池的七个参数及工作原理做讲解。

阅读完本文能够通过自己的语言简单阐述线程池的工作原理和画出原理图。

一、面试题

在看很多Java面试题相关的文章或者博客中,对于线程池都会有这么几个常见连环问题。

👨‍💻面试官:

  1. 平时工作中使用到线程了吗?线程池的优势是什么?
  2. 请介绍使用线程池的方式。
  3. 可以给我分别介绍一下线程池的七个参数的概念和作用吗?、
  4. 线程池的工作原理是什么的?画一下线程池的工作原理图。
  5. 为什么不建议使用Executors创建线程,而使用ThreadPoolExecutor实现类来创建线程?
  6. 线程池如何配置合理的线程数。

问题,都是一步一步深入。

我们在回答的时候,第一要做到的就是尽可能的让面试官往自己更加有把握的地方问过去。


二、线程池的七个参数

我们平常在使用线程池时,不是使用Executors工具类来创建的,而是显式的使用ThreadPoolExecutor来创建的。

原因:

【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。 说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

来自于阿里的Java开发手册

接下来来看我们今天的重点哈:

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

一个一个参数分析:

  1. corePoolSize – 核心线程数。要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
  2. maximumPoolSize – 池中能够容纳同时执行的最大线程数,此值必须大于等于1.
  3. keepAliveTime – 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
    当前线程池数量超过corePoolSize时,当空闲时间达到KeepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。
  4. unit keepAliveTime参数的时间单位
  5. workQueue – 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
  6. threadFactory – 执行程序创建新线程时使用的工厂 ,一般用默认即可。
  7. handler – 执行被阻塞时使用的处理程序,因为达到了线程边界和队列容量。平常称为拒绝策略。

看完这个概念一下肯定是没啥感觉的,我会用一个生活中的案列来讲解线程池的工作原理,确保大家都能理解。

三、线程池的工作原理

3.1、原理图

1704461035754.jpg

先说说这张图:

1)当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:

  • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
  • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入阻塞队列中,进行排队等待;
  • 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
  • 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

2)当一个线程完成任务时,它会从队列中取下一个任务来执行

3)当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:

  1. 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。
  2. 所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。

4)拒绝策略,Jdk默认的拒绝策略有以下四种:

  1. AbortPolicy: 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
  2. CallerRunsPolicy: 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。
  3. DiscardPolicy: 直接丢弃.
  4. DiscardOldestPolicy: 触发拒绝策略时,只要线程池没有关闭的话,丢弃阻塞队列 workQueue中最老的任务,并将新任务加入

上面这个内容我第一次看的时候,也稍稍有些懵,学习的时候懂,过段时间再看又是陌生人啦。

这次为了让记忆更为深刻,举一个特别形象生动的例子。源自于 尚硅谷--周阳老师

3.2、生活案例

相信我们大家肯定都去过银行哈。这次就是以银行来举例的。先看看场景

1704461048283.jpg

我来解释下哈:

  1. 一名顾客来到了银行,发现没人在办理业务,直接就到值班1号窗口开始办理业务。
  2. 接着又来了一名顾客,2号窗口没人,就走到了值班2号窗口开始办理业务。
  3. 紧接着又来了第三名、第四名、第五名顾客,现在值班1、2号窗口都正在办理业务,他们就坐在等候区开始等待。
  4. 但是这时候又来了第六名、第七名、第八名顾客。这时候,等候区已满,大堂经理看到这个情况,马上告诉行长,需要增加临时办理窗口。
  5. 问题来了?这个时候增添的临时窗口3、4、5会让谁先去办理业务呢???
    是后来的第六、七、八名顾客,还是之前就在等候区的第三、四、五名顾客呢??
  6. 会处理刚进来的第六、七、八名顾客。
  7. 原因:在上文我说过,当调用 execute()方法添加一个请求任务时,线程池会做出如下判断:
    如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;所以会立刻处理第6、7、8名顾客业务。
  8. 继续,如果接着还有很多顾客过来处理业务,这时候大堂经理看到这种情况,就触发拒绝策略。(这种情况就是陌生的哈,那个经理敢把顾客往外赶哈)

接下来我们用代码来模拟这个场景哈。

四、通过代码示例来进一步分析参数

设置的数据就按照图上的来,

corePoolSize核心线程数为:2,maximumPoolSize最大线程数为:5,keepAliveTime:等待时间为:3秒

workQueue阻塞队列为:3,ThreadFactory线程的创建方式就使用默认的:Executors.defaultThreadFactory()

RejectedExecutionHandler拒绝策略:都会测试一遍。

public static void main(String[] args) {
        BlockingDeque<Runnable> workQueue = new LinkedBlockingDeque<Runnable>(3);
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2,
                5,
                3, TimeUnit.SECONDS,
                workQueue,
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0; i <8 ; i++) {
            final  int temp=i;
            executor.execute(()->{
                System.out.println(Thread.currentThread().getName()+"\t"+"执行第 "+temp+" 个任务!!");
            });
        }
    }

这个代码里设置的拒绝策略为:new ThreadPoolExecutor.AbortPolicy(),超过maximumPoolSize+workQueue之和的数据。就会直接抛出异常,停止运行。

当设置为8的时候,还是可以正常的,我们调到9个任务来试一试。

1704461060130.jpg

当我们向上调整上,任务超过最大数,就会触发拒绝策略。

1704461104506.jpg

将策略改为new ThreadPoolExecutor.DiscardPolicy());直接就抛弃了第八个任务。

1704461085462.jpg

策略改为:new ThreadPoolExecutor.CallerRunsPolicy(),当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。

1704461113134.jpg

还有最后一个DiscardOldestPolicy: 触发拒绝策略时,只要线程池没有关闭的话,丢弃阻塞队列 workQueue中最老的任务,并将新任务加入

五、自言自语

希望本篇文章能让你感到有所收获!!!

我们:待别日相见时,都已有所成

无论在外如何如何 还是想要回到家。

车站它要是能够记录故事,他应该记录过许许多多的悲欢离合吧。


目录
相关文章
|
2月前
|
存储 缓存 Java
什么是线程池?从底层源码入手,深度解析线程池的工作原理
本文从底层源码入手,深度解析ThreadPoolExecutor底层源码,包括其核心字段、内部类和重要方法,另外对Executors工具类下的四种自带线程池源码进行解释。 阅读本文后,可以对线程池的工作原理、七大参数、生命周期、拒绝策略等内容拥有更深入的认识。
120 29
什么是线程池?从底层源码入手,深度解析线程池的工作原理
|
10天前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
11 1
|
29天前
|
Java 编译器 程序员
【多线程】synchronized原理
【多线程】synchronized原理
51 0
|
29天前
|
设计模式 Java 物联网
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
【多线程-从零开始-玖】内核态,用户态,线程池的参数、使用方法详解
42 0
|
29天前
|
Java 应用服务中间件 API
nginx线程池原理
nginx线程池原理
26 0
|
2月前
|
安全 Java 调度
python3多线程实战(python3经典编程案例)
该文章提供了Python3中多线程的应用实例,展示了如何利用Python的threading模块来创建和管理线程,以实现并发执行任务。
31 0
|
2月前
|
存储 缓存 Java
JAVA并发编程系列(11)线程池底层原理架构剖析
本文详细解析了Java线程池的核心参数及其意义,包括核心线程数量(corePoolSize)、最大线程数量(maximumPoolSize)、线程空闲时间(keepAliveTime)、任务存储队列(workQueue)、线程工厂(threadFactory)及拒绝策略(handler)。此外,还介绍了四种常见的线程池:可缓存线程池(newCachedThreadPool)、定时调度线程池(newScheduledThreadPool)、单线程池(newSingleThreadExecutor)及固定长度线程池(newFixedThreadPool)。
|
3月前
|
存储 NoSQL Java
线程池的原理与C语言实现
【8月更文挑战第22天】线程池是一种多线程处理框架,通过复用预创建的线程来高效地处理大量短暂或临时任务,提升程序性能。它主要包括三部分:线程管理器、工作队列和线程。线程管理器负责创建与管理线程;工作队列存储待处理任务;线程则执行任务。当提交新任务时,线程管理器将其加入队列,并由空闲线程处理。使用线程池能减少线程创建与销毁的开销,提高响应速度,并能有效控制并发线程数量,避免资源竞争。这里还提供了一个简单的 C 语言实现示例。
|
2月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
29 0
|
25天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程