`
frank1998819
  • 浏览: 752107 次
  • 性别: Icon_minigender_1
  • 来自: 南京
文章分类
社区版块
存档分类

JAVA 线程池的正确打开方式(转)

    博客分类:
  • Java
 
阅读更多

前环境

  1. jdk == 1.8

Executors 使用的隐患

先来看一段代码,我们要创建一个固定线程池,假设固定线程数是4。代码如下:

Executors是JAVA并发包中提供的,用来快速创建不同类型的线程池。

是不是很简单,创建线程池只需一行代码。对于一些个人项目或临时性的项目,这样写确实没什么问题,而且开发速度很快。但在一些大型项目中,这种做法一般是禁止的。

WHY???

因为用Executors创建的线程池存在性能隐患,我们看一下源码就知道,用Executors创建线程池时,使用的队列是new LinkedBlockingQueue<Runnable>(),这是一个无边界队列,如果不断的往里加任务时,最终会导致内存问题,也就是说在项目中由于使用了无边界队列,导致的内存占用的不可控性。下图是不断添加线程任务导致老年代被占满的情况:

当然,除了内存问题,它还存在一些其他的问题,在下面对线程池参数的介绍中会具体说明。

线程池的正确创建方式

其实,问题很好解决。提供的简便方式有局限性,那我们自己new一个ThreadPoolExecutor,无非多写几行代码而已。

关于ThreadPoolExecutor的具体代码如下:

参数说明:

  • corePoolSize:核心线程数;
  • maximumPoolSize:最大线程数,即线程池中允许存在的最大线程数;
  • keepAliveTime:线程存活时间,对于超过核心线程数的线程,当线程处理空闲状态下,且维持时间达到keepAliveTime时,线程将被销毁;
  • unit:keepAliveTime的时间单位
  • workQueue:工作队列,用于存在待执行的线程任务;
  • threadFactory:创建线程的工厂,用于标记区分不同线程池所创建出来的线程;
  • handler:当到达线程数上限或工作队列已满时的拒绝处理逻辑;

具体代码

  • 自定义threadFactory。除了可以自定义创建的线程名称,方便问题排查,在newThread(Runnable r)创建线程的方法中,还可以进行定制化设置,如为线程设置特定上下文等。

  • 自定义RejectedExecutionHandler。记录异常信息,选择不同处理逻辑,有交由当前线程执行任务,有直接抛出异常,再或者等待后继续添加任务等。

  • 创建自定义线程池

线程池内在处理逻辑

我们通过一些例子,来观察一下其内部的处理逻辑。基于上述具体代码,我们已经创建了一个核心线程数4,最大线程数8,线程存活时间10s,工作队列最大容量为10的一个线程池。

  • 初始化线程池:未添加线程任务

    • 这时,线程池中***不会创建任何线程***,存活线程为0,工作队列为0.
  • 未达核心线程数:添加4个线程任务

    • 由于当前存活线程数 <= 核心线程数,所以会***创建新的线程***。即存活线程为4,工作队列为0.
  • 核心线程数已满:添加第5个线程任务

    • 若当前线程池中存在空闲线程,则交由该线程处理。即存活线程为4,工作队列为0.
    • 若当前所有线程处理运行状态,加入工作队列。即存活线程为4,工作队列为1.(注意:此时工作队列中的任务不会被执行,直到有线程空闲后,才能被处理
  • 工作队列未满:假设添加的任务都是耗时操作(短时间不会结束),再添加9个耗时任务

    • 即存活线程为4,工作队列为10.
  • 工作队列已满 & 未达最大线程数:再添加4个任务

    • 当工作队列已满,且不存在空闲线程,此时会***创建额外线程***来处理当前任务。此时存活线程为8,工作队列为10.
  • 工作队列已满 & 且最大线程数已满:再添加1个任务

    • 触发RejectedExecutionHandler,将当前任务交由自己设置的执行句柄进行处理。此时存活线程为8,工作队列为10.
  • 当任务执行完后,没有新增的任务,临时扩充的线程(大于核心线程数的)将在10s(keepAliveTime)后被销毁。

总结

最后,我们在使用线程池的时候,需要根据使用场景来自行选择。通过corePoolSize和maximumPoolSize的搭配,存活时间的选择,以及改变队列的实现方式,如:选择延迟队列,来实现定时任务的功能。并发包Executors中提供的一些方法确实好用,但我们仍需有保留地去使用,这样在项目中就不会挖太多的坑。

扩展

对于一些耗时的IO任务,盲目选择线程池往往不是最佳方案。通过异步+单线程轮询,上层再配合上一个固定的线程池,效果可能更好。类似与Reactor模型中selector轮询处理。

 

分享到:
评论

相关推荐

    java 版视频转换 工具

    10. **资源管理**:在处理结束后,确保所有打开的文件、网络连接和解码器资源都被正确关闭,这是防止资源泄漏的关键。 总结来说,开发Java版的视频转换工具涵盖了多媒体处理、文件操作、并发编程等多个核心IT技术,...

    Java2学习指南

    Java2学习指南是一份专为Java初学者和进阶者准备的宝贵资源,它涵盖了Java编程语言的基础到高级概念,帮助读者深入理解并熟练运用Java技术。这份文档旨在提供一个全面的学习路径,让读者能够逐步掌握Java编程的核心...

    java模拟微信浏览器访问.rar

    模拟访问时需要正确管理这些Cookie,通常通过`CookieManager`和`CookiePolicy`实现。 4. **POST 请求与数据编码**: - 当需要提交表单或发送JSON数据时,需要构造POST请求。使用`HttpURLConnection`或第三方库时,...

    (PDF带目录)《Java 并发编程实战》,java并发实战,并发

    Java通过`Thread`类支持线程的创建和管理,而`Runnable`接口提供了另一种实现多线程的方式。 2. **同步机制**:Java中的同步机制包括`synchronized`关键字、`wait()`, `notify()`和`notifyAll()`方法,以及`Lock`...

    java获取压缩文件的名称并解压

    这里的`try-with-resources`结构确保了`ZipFile`在使用完毕后会被正确关闭。 现在我们转向多线程解压。Java的`ExecutorService`和`Future`可以帮助我们实现这一目标。我们可以创建一个固定大小的线程池,然后为每个...

    Java实现UDP穿透NAT技术

    Java提供了一种方式来实现UDP NAT穿透,通过创建一个服务器端和客户端应用程序,可以协助位于NAT后的设备之间建立直接通信。以下是实现这一技术的一些关键知识点: 1. **STUN(Traversal Using Relays around NAT)...

    java Task

    Java中的JUnit是一个常用的单元测试框架,开发者可以编写测试用例,对UrlUtil或其他类的功能进行断言和验证,确保代码的正确性和稳定性。 3. **BuildTask.java**: 文件名暗示这可能与创建或构建任务有关。在Java...

    Java SE 类库查询手册

    Java SE 类库查询手册是Java开发人员的重要参考资料,它涵盖了Java标准版平台的核心类库,这些类库提供了大量用于构建应用程序的基础工具和功能。在Java SE中,类库包括了集合框架、输入/输出流、网络编程、多线程、...

    java常见错误分析20例电子书

    书里会涵盖如何正确打开、读写和关闭文件,以及异常处理策略。 6. **空集合异常**:调用空集合的`iterator().next()`会抛出`NoSuchElementException`。了解集合的状态检查和安全的迭代方式很重要。 7. **除数为零...

    JAVA短信猫开发包源码

    Java短信猫开发包源码是针对短信猫设备进行二次开发的工具,主要目的是为了通过编程方式控制短信猫发送和接收短信。短信猫是一种硬件设备,它能够连接到计算机并模拟手机的功能,允许用户通过电脑来收发短信。在Java...

    java版本仿IE下载

    下载的文件会被保存到本地,需要正确处理文件的创建、打开、写入、关闭等操作,保证数据完整无误。 7. **异常处理** 网络下载过程中可能会遇到各种问题,如网络中断、服务器错误等,因此,良好的异常处理机制至关...

    java多线程多点续传demo

    结合`DownLoad.java`和`DownLoadTask.java`,我们可以推测`DownLoad`可能是主类,负责整体的控制逻辑,包括初始化线程池,分配任务,以及管理下载进度和状态。而`DownLoadTask.java`可能代表一个具体的下载任务,每...

    IDEA及java规范.zip

    插件是IDEA增强功能的重要方式,如Lombok插件支持自动插入getter、setter和构造函数,Alibaba Java Coding Guidelines插件可以帮助开发者遵循阿里巴巴的Java编程规范,检查代码中的潜在问题。还有Git图形化管理工具...

    Java文件切割器源代码

    `java.util.concurrent`包提供了线程池和并发工具,可以用于这类任务。 7. **文件合并**:除了切割,文件切割器通常还包括合并功能,将切割后的文件恢复为原始文件。这涉及到读取所有小文件,并按照正确的顺序将...

    java qqWry.dat多个IP同时查询代码

    1. **文件读取**:使用Java的`FileInputStream`类来打开和读取qqWry.dat文件。由于文件是二进制的,所以需要设置正确的读取模式。 2. **缓冲区处理**:为了提高效率,我们可以使用`BufferedInputStream`对数据流...

    java会话管理、多线程.docx

    3. 使用ExecutorService和Future:这是Java 5引入的高级多线程工具,提供了更灵活的线程池管理,可以控制线程的生命周期,避免资源浪费。 线程交互通常通过共享数据或通过线程安全的方式来实现,而线程同步是确保多...

    java多線程導入不同csv文件到不同表1

    Java的`BufferedReader`或`FileInputStream`在打开文件时,可以指定字符编码,例如`StandardCharsets.UTF_8`,以确保正确读取非ASCII字符。同时,对于输出的注释或日志,也应该确保使用正确的编码写入。 最后,虽然...

    java 模拟快车下载器

    Java的并发库提供ExecutorService和ThreadPoolExecutor,可以用来创建和管理线程池。每个线程负责下载文件的一部分。为了协调各个线程,可以使用CountDownLatch或CyclicBarrier等同步工具。线程间需要共享已下载的总...

Global site tag (gtag.js) - Google Analytics