【Java并发编程】面试必备之线程池

简介: 【Java并发编程】面试必备之线程池

什么是线程池

  • 是一种基于池化思想管理线程的工具。

池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需。比如我们的对象池,数据库连接池等。

线程池好处

我们为什么要使用线程池,直接new thread start不好吗?

  • 降低资源消耗: 通过重复利用已创建的线程来降低线程创建和销毁所造成的消耗。
  • 提高响应速度: 任务到达时,可以立即执行,不需要等到线程创建再来执行任务。
  • 提高线程的可管理性: 线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。

线程池的执行流程

我们先来看看线程池的一个执行流程图,此图来自文末参考1
在这里插入图片描述

通过上述图我们可以得出线程池执行任务可以有以下几种情况:

  • 如果当前的运行线程小于coreSize,则创建新线程来执行任务。
  • 如果当前运行的线程等于coreSize或多余coreSize(动态修改了coreSize才会出现这种情况),把任务放到阻塞队列中。
  • 如果队列已满无法将新加入的任务放进去的话,则需要创建新的线程来执行任务。
  • 如果新创建线程已经达到了最大线程数,任务将会被拒绝。

怎么是用线程池

java jdkExecutors有提供创建不同线程池的方法(一般不推荐这种做法)阿里巴巴的开发手册也明确强制规定不让通过Executors来创建的,在一些公司的开发规范里面应该也会有这么一条吧。
在这里插入图片描述

  • newFixedThreadPool
  • newSingleThreadExecutor
  • newCachedThreadPool
  • newScheduledThreadPool
  • newWorkStealingPool (jdk1.8新增的)

我们可以使用ThreadPoolExecutor来创建线程池

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

我们可以看出创建线程池有七个参数,而上述我们通过Executors工具类来创建的线程池就一两个参数,其他参数它都帮我们默认写死了,我们只有真正理解了这几个参数才能更好的去是用线程池。下面我们来看看这七个参数(线程池参数)。

corePoolSize

  • 核心线程数(线程池的基本大小)当我们提交一个任务到线程池时就会创建一个线程来执行任务.当我们需要执行的任务数大于核心线程数了就不再创建,

如果我们调用了prestartAllCoreThreads()方法线程池就会为我们提前创建好所有的基本线程。

maximumPoolSize

  • 最大线程数:线程池允许创建的最大线程数。如果队列已经满了,且已创建的线程数小于最大线程数,则线程池就会创建新的线程来执行任务。这里有个小知识点,如果我们的队列是用的无界队列,这个参数是不会起作用的,因为我们的任务会一直往队列中加,队列永远不会满(内存允许的情况)。

keepAliveTime

  • 空闲线程最大生存时间。当前线程数大于核心线程数时,结束多余的空闲线程等待新任务的最长时间。

默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
比如当前线程池中最大线程数(maximumPoolSize)为50,核心线程数(corePoolSize)为10,当前正在跑任务的线程数为30.然后是不是空出了20个线程没活干,所以这20个线程就要被消毁,有点卸磨杀驴的感觉。如果剩下的30个线程干完活了也休息了keepAliveTime这么久,然后这30个线程里面也要被销毁20个,就保留个核心线程。如果设置了allowCoreThreadTimeOut等于true核心线程也会被销毁。
就跟我们做外包项目一样,甲方项目完成了就得去另外一个甲方,如果短时间内都没有甲方接纳你的话,你就要被辞退了,只会留下几个核心人员维护下项目,如果甲方项目维护的话用自己的人的话,所有的外包人会都会被辞退。

unit

  • 线程存活时间的的单位。可选的单位有dayshours等。

workQueue

任务队列。可以选择以下这些队列
在这里插入图片描述

threadFactory

用户设置创建线程的工厂,我们可以通过这个工厂来创建有业务意义的线程名字。我们可以对比下自定义的线程工厂和默认的线程工厂创创建的名字。

默认产生线程的名字 自定义线程工厂产生名字
pool-5-thread-1 testPool-1-thread-1

阿里开发手册也有明确说到,需要指定有意义的线程名字。
在这里插入图片描述

RejectedExecutionHandler

  • 线程池拒绝策略。当队列和线程池都满了说明线程池已经处于饱和状态。 必须要采取一定的策略来处理新提交的任务。jdk默认提供了四种拒绝策略:
    在这里插入图片描述

其实我们也可以自定义任务拒绝策略(实现下RejectedExecutionHandler接口),比如说如果任务拒绝了我们可以记录下日志,或者重试等,根据自己的业务需求来实现。

  • dubbo 任务拒绝策略

     @Override
       public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
           String msg = String.format("Thread pool is EXHAUSTED!" +
                   " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: "
                   + "%d)," +
                   " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s://%s:%d!",
               threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(),
               e.getLargestPoolSize(),
               e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(),
               url.getProtocol(), url.getIp(), url.getPort());
           logger.warn(msg);
           dumpJStack();
           dispatchThreadPoolExhaustedEvent(msg);
           throw new RejectedExecutionException(msg);
我们可以看出`dubbo`的拒绝策略主要记录了详细的级别为warm的日志、输出当前线程堆栈详情、继续抛出拒绝任务异常。
### 线程池参数如何设置?
线程池既然有这么多参数那么我们如何去根据自己的业务实际情况来去合理的设置每个参数?
- 一般我们如果任务为耗时`IO`型比如读取数据库、文件读写以及网略通信的的话这些任务不会占据很多`cpu`的资源但是会比较耗时:线程数设置为2倍CPU数以上,充分的来利用`CPU`资源。
- 一般我们如果任务为CPU密集型的话比如大量计算、解压、压缩等这些操作都会占据大量的cpu。所以针对于这种情况的话一般设置线程数为:1倍cpu+1。为啥要加1,很多说法是备份线程。
- 如果既有IO密集型任务,又有`CPU`密集型任务,这种该怎么设置线程大小?这种的话最好分开用线程池处理,`IO`密集的用`IO`密集型线程池处理,`CPU`密集型的用cpu密集型处理。
以上都只是理算情况下的估算而已,真正的合理参数还是需要看看实际生产运行的效果来合理的调整的。
### 监控线程池
- 线程池工作是否饱和?线程的情况如何?总共执行了多少个任务?现在线程池的运行情况如何?队列里面是否有堆积任务?面对上面这些问题,线程池也有提供一些方法可以让我们来查看上面这些指标。
![在这里插入图片描述](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/919ea0f8115042eda2200fb7b7e306b7~tplv-k3u1fbpfcp-zoom-1.image)
有了这些参数我们是不是调整线程池的参数就更加方便了。或者根据线程池的活跃程度我们自动来调节(动态调整下篇再来说)线程池的参数。
### 关于线程池的几个问题
- 线程池是否区分核心线程和非核心线程?
- 如何保证核心线程不被销毁?
- 线程池的线程是如何做到复用的?
以上几个小问题我们去看看线程池的源码,这几个问题应该就不成问题了,我们下篇见。
### 结束
- 由于自己才疏学浅,难免会有纰漏,假如你发现了错误的地方,还望留言给我指出来,我会对其加以修正。
- 如果你觉得文章还不错,你的转发、分享、赞赏、点赞、留言就是对我最大的鼓励。
- 感谢您的阅读,十分欢迎并感谢您的关注。
![在这里插入图片描述](//p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/00319227cf25478e8ed7047fc07aeaff~tplv-k3u1fbpfcp-zoom-1.image)
- 巨人肩膀摘苹果
https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html
《java并发编程实战》




 





 
目录
相关文章
|
14天前
|
Java 程序员
Java编程中的异常处理:从基础到高级
在Java的世界中,异常处理是代码健壮性的守护神。本文将带你从异常的基本概念出发,逐步深入到高级用法,探索如何优雅地处理程序中的错误和异常情况。通过实际案例,我们将一起学习如何编写更可靠、更易于维护的Java代码。准备好了吗?让我们一起踏上这段旅程,解锁Java异常处理的秘密!
|
16天前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
18天前
|
Java 程序员
Java社招面试题:& 和 && 的区别,HR的套路险些让我翻车!
小米,29岁程序员,分享了一次面试经历,详细解析了Java中&和&&的区别及应用场景,展示了扎实的基础知识和良好的应变能力,最终成功获得Offer。
45 14
|
16天前
|
缓存 Java 调度
多线程编程核心:上下文切换深度解析
在现代计算机系统中,多线程编程已成为提高程序性能和响应速度的关键技术。然而,多线程编程中一个不可避免的概念就是上下文切换(Context Switching)。本文将深入探讨上下文切换的概念、原因、影响以及优化策略,帮助你在工作和学习中深入理解这一技术干货。
36 10
|
18天前
|
缓存 Java 开发者
Java多线程编程的陷阱与最佳实践####
本文深入探讨了Java多线程编程中常见的陷阱,如竞态条件、死锁和内存一致性错误,并提供了实用的避免策略。通过分析典型错误案例,本文旨在帮助开发者更好地理解和掌握多线程环境下的编程技巧,从而提升并发程序的稳定性和性能。 ####
|
11天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
11天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
32 3
|
12天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####
|
16天前
|
算法 调度 开发者
多线程编程核心:上下文切换深度解析
在多线程编程中,上下文切换是一个至关重要的概念,它直接影响到程序的性能和响应速度。本文将深入探讨上下文切换的含义、原因、影响以及如何优化,帮助你在工作和学习中更好地理解和应用多线程技术。
25 4
|
16天前
|
开发框架 安全 Java
Java 反射机制:动态编程的强大利器
Java反射机制允许程序在运行时检查类、接口、字段和方法的信息,并能操作对象。它提供了一种动态编程的方式,使得代码更加灵活,能够适应未知的或变化的需求,是开发框架和库的重要工具。
34 2