服务端应用程序(如数据库和 Web 服务器)需要处理来自客户端的高并发、耗时较短的请求任务,所以频繁的创建处理这些请求的所需要的线程就是一个非常消耗资源的操作。常规的方法是针对一个新的请求创建一个新线程,虽然这种方法似乎易于实现,但它有重大缺点。为每个请求创建新线程将花费更多的时间,在创建和销毁线程时花费更多的系统资源。因此同时创建太多线程的 JVM 可能会导致系统内存不足,这就需要限制要创建的线程数,也就是需要使用到线程池。

一、什么是 Java 中的线程池?

线程池技术就是线程的重用技术,使用之前创建好的线程来执行当前任务,并提供了针对线程周期开销和资源冲突问题的解决方案。 由于请求到达时线程已经存在,因此消除了线程创建过程导致的延迟,使应用程序得到更快的响应。

  • Java提供了以Executor接口及其子接口ExecutorServiceThreadPoolExecutor为中心的执行器框架。通过使用Executor,完成线程任务只需实现 Runnable接口并将其交给执行器执行即可。
  • 为您封装好线程池,将您的编程任务侧重于具体任务的实现,而不是线程的实现机制。
  • 若要使用线程池,我们首先创建一个 ExecutorService对象,然后向其传递一组任务。ThreadPoolExcutor 类则可以设置线程池初始化和最大的线程容量。

上图表示线程池初始化具有3 个线程,任务队列中有5 个待运行的任务对象。

执行器线程池方法

方法 描述
newFixedThreadPool(int) 创建具有固定的线程数的线程池,int参数表示线程池内线程的数量
newCachedThreadPool() 创建一个可缓存线程池,该线程池可灵活回收空闲线程。若无空闲线程,则新建线程处理任务。
newSingleThreadExecutor() 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行

在固定线程池的情况下,如果执行器当前运行的所有线程,则挂起的任务将放在队列中,并在线程变为空闲时执行。

二、线程池示例

在下面的内容中,我们将介绍线程池的executor执行器。

创建线程池处理任务要遵循的步骤

  1. 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑
  2. 使用Executors创建线程池ExecutorService
  3. 将待执行的任务对象交给ExecutorService进行任务处理
  4. 停掉 Executor 线程池
  1. //第一步: 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑 (Step 1)
  2. class Task implements Runnable {
  3. private String name;
  4. public Task(String s) {
  5. name = s;
  6. }
  7. // 打印任务名称并Sleep 1秒
  8. // 整个处理流程执行5次
  9. public void run() {
  10. try{
  11. for (int i = 0; i<=5; i++) {
  12. if (i==0) {
  13. Date d = new Date();
  14. SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
  15. System.out.println("任务初始化" + name +" = " + ft.format(d));
  16. //第一次执行的时候,打印每一个任务的名称及初始化的时间
  17. }
  18. else{
  19. Date d = new Date();
  20. SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
  21. System.out.println("任务正在执行" + name +" = " + ft.format(d));
  22. // 打印每一个任务处理的执行时间
  23. }
  24. Thread.sleep(1000);
  25. }
  26. System.out.println("任务执行完成" + name);
  27. } catch(InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }

测试用例

  1. public class ThreadPoolTest {
  2. // 线程池里面最大线程数量
  3. static final int MAX_SIZE = 3;
  4. public static void main (String[] args) {
  5. // 创建5个任务
  6. Runnable r1 = new Task("task 1");
  7. Runnable r2 = new Task("task 2");
  8. Runnable r3 = new Task("task 3");
  9. Runnable r4 = new Task("task 4");
  10. Runnable r5 = new Task("task 5");
  11. // 第二步:创建一个固定线程数量的线程池,线程数为MAX_SIZE
  12. ExecutorService pool = Executors.newFixedThreadPool(MAX_SIZE);
  13. // 第三步:将待执行的任务对象交给ExecutorService进行任务处理
  14. pool.execute(r1);
  15. pool.execute(r2);
  16. pool.execute(r3);
  17. pool.execute(r4);
  18. pool.execute(r5);
  19. // 第四步:关闭线程池
  20. pool.shutdown();
  21. }
  22. }

示例执行结果

  1. 任务初始化task 1 = 05:25:55
  2. 任务初始化task 2 = 05:25:55
  3. 任务初始化task 3 = 05:25:55
  4. 任务正在执行task 3 = 05:25:56
  5. 任务正在执行task 1 = 05:25:56
  6. 任务正在执行task 2 = 05:25:56
  7. 任务正在执行task 1 = 05:25:57
  8. 任务正在执行task 3 = 05:25:57
  9. 任务正在执行task 2 = 05:25:57
  10. 任务正在执行task 3 = 05:25:58
  11. 任务正在执行task 1 = 05:25:58
  12. 任务正在执行task 2 = 05:25:58
  13. 任务正在执行task 2 = 05:25:59
  14. 任务正在执行task 3 = 05:25:59
  15. 任务正在执行task 1 = 05:25:59
  16. 任务正在执行task 1 = 05:26:00
  17. 任务正在执行task 2 = 05:26:00
  18. 任务正在执行task 3 = 05:26:00
  19. 任务执行完成task 3
  20. 任务执行完成task 2
  21. 任务执行完成task 1
  22. 任务初始化task 5 = 05:26:01
  23. 任务初始化task 4 = 05:26:01
  24. 任务正在执行task 4 = 05:26:02
  25. 任务正在执行task 5 = 05:26:02
  26. 任务正在执行task 4 = 05:26:03
  27. 任务正在执行task 5 = 05:26:03
  28. 任务正在执行task 5 = 05:26:04
  29. 任务正在执行task 4 = 05:26:04
  30. 任务正在执行task 4 = 05:26:05
  31. 任务正在执行task 5 = 05:26:05
  32. 任务正在执行task 4 = 05:26:06
  33. 任务正在执行task 5 = 05:26:06
  34. 任务执行完成task 4
  35. 任务执行完成task 5

如程序执行结果中显示的一样,任务 4 或任务 5 仅在池中的线程变为空闲时才执行。在此之前,额外的任务将放在待执行的队列中。

线程池执行前三个任务,线程池内线程回收空出来之后再去处理执行任务 4 和 5

使用这种线程池方法的一个主要优点是,假如您希望一次处理10000个请求,但不希望创建10000个线程,从而避免造成系统资源的过量使用导致的宕机。您可以使用此方法创建一个包含500个线程的线程池,并且可以向该线程池提交500个请求。

ThreadPool此时将创建最多500个线程,一次处理500个请求。在任何一个线程的进程完成之后,ThreadPool将在内部将第501个请求分配给该线程,并将继续对所有剩余的请求执行相同的操作。在系统资源比较紧张的情况下,线程池是保证程序稳定运行的一个有效的解决方案。

三、使用线程池的注意事项与调优

  1. 死锁: 虽然死锁可能发生在任何多线程程序中,但线程池引入了另一个死锁案例,其中所有执行线程都在等待队列中某个阻塞线程的执行结果,导致线程无法继续执行。
  2. 线程泄漏 : 如果线程池中线程在任务完成时未正确返回,将发生线程泄漏问题。例如,某个线程引发异常并且池类没有捕获此异常,则线程将异常退出,从而线程池的大小将减小一个。如果这种情况重复多次,则线程池最终将变为空,没有线程可用于执行其他任务。
  3. 线程频繁轮换: 如果线程池大小非常大,则线程之间进行上下文切换会浪费很多时间。所以在系统资源允许的情况下,也不是线程池越大越好。

线程池大小优化: 线程池的最佳大小取决于可用的处理器数量和待处理任务的性质。对于CPU密集型任务,假设系统有N个逻辑处理核心,N 或 N+1 的最大线程池数量大小将实现最大效率。对于 I/O密集型任务,需要考虑请求的等待时间(W)和服务处理时间(S)的比例,线程池最大大小为 N*(1+ W/S)会实现最高效率。

不要教条的使用上面的总结,需要根据自己的应用任务处理类型进行灵活的设置与调优,其中少不了测试实验。

欢迎关注我的博客,里面有很多精品合集

  • 本文转载注明出处(必须带连接,不能只转文字):字母哥博客

觉得对您有帮助的话,帮我点赞、分享!您的支持是我不竭的创作动力! 。另外,笔者最近一段时间输出了如下的精品内容,期待您的关注。

详解线程池的作用及Java中如何使用线程池的更多相关文章

  1. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  2. meta标签详解(meta标签的作用)///////////////////////////转

    meta标签详解(meta标签的作用) 很多人却忽视了HTML标签META的强大功效,一个好的META标签设计可以大大提高你的个人网站被搜索到的可能性,有兴趣吗,谁我来重新认识一下META标签吧   ...

  3. Java中的字符串常量池,栈和堆的概念

    问题:String str = new String(“abc”),“abc”在内存中是怎么分配的?    答案是:堆内存.(Tips:jdk1.8 已经将字符串常量池放在堆内存区) 题目考查的为Ja ...

  4. Java 中如何实现线程间通信

    世界以痛吻我,要我报之以歌 -- 泰戈尔<飞鸟集> 虽然通常每个子线程只需要完成自己的任务,但是有时我们希望多个线程一起工作来完成一个任务,这就涉及到线程间通信. 关于线程间通信本文涉及到 ...

  5. [译]线程生命周期-理解Java中的线程状态

    线程生命周期-理解Java中的线程状态 在多线程编程环境下,理解线程生命周期和线程状态非常重要. 在上一篇教程中,我们已经学习了如何创建java线程:实现Runnable接口或者成为Thread的子类 ...

  6. Java中的守护线程 & 非守护线程(简介)

    Java中的守护线程 & 非守护线程 守护线程 (Daemon Thread) 非守护线程,又称用户线程(User Thread) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守 ...

  7. java中等待所有线程都执行结束(转)

    转自:http://blog.csdn.net/liweisnake/article/details/12966761 今天看到一篇文章,是关于java中如何等待所有线程都执行结束,文章总结得很好,原 ...

  8. java中等待所有线程都执行结束

    转自:http://blog.csdn.net/liweisnake/article/details/12966761 今天看到一篇文章,是关于java中如何等待所有线程都执行结束,文章总结得很好,原 ...

  9. Java中怎样创建线程安全的方法

    面试问题: 下面的方法是否线程安全?怎样让它成为线程安全的方法? class MyCounter { private static int counter = 0; public static int ...

随机推荐

  1. Scrum冲刺_Day06

    一.团队展示: 1.项目:light_note备忘录 2.队名:删库跑路队 3.团队成员 队员(不分先后) 项目角色 黄敦鸿 后端工程师.测试 黄华 后端工程师.测试 黄骏鹏 后端工程师.测试 黄源钦 ...

  2. AcWing 324. 贿赂FIPA

    题目链接 大型补档计划 \(f[i][j]\) 表示第 \(i\) 个国家,获得 \(j\) 个国家支持,用的最少花费 \(f[i][0] = 0\) \(f[i][sz[i]] = w[i]\) 对 ...

  3. Swagger2配置

    配置类 package top.yalong; import org.springframework.beans.factory.annotation.Value; import org.spring ...

  4. [打基础]luogu2181对角线——计数原理

    啦啦啦我ysw又回来啦!之后大概会准备打acm,暑假尽量复习复习,因为已经快两年没碰oi了,最多也就高三noip前学弟学妹出题讲题,所以从这一篇blog开始大概会有一系列"打基础" ...

  5. Spark-3-调优要点

    1 内存调整要点 Memory Tuning,Java对象会占用原始数据2~5倍甚至更多的空间.最好的检测对象内存消耗的办法就是创建RDD,然后放到cache里面去,然后在UI上面看storage的变 ...

  6. json JSON_UNESCAPED_UNICODE 防止中文乱码

    json_encode(['content'=>$content],JSON_UNESCAPED_UNICODE)

  7. 从源码角度学习Java动态代理

    前言 最近,看了一下关于RMI(Remote Method Invocation)相关的知识,遇到了一个动态代理的问题,然后就决定探究一下动态代理. 这里先科普一下RMI. RMI 像我们平时写的程序 ...

  8. python一键搭建ftp服务

    from pyftpdlib.authorizers import DummyAuthorizer from pyftpdlib.handlers import FTPHandler from pyf ...

  9. C# 数据结构与算法 操作系统原理 计算机网络原理 数据库开发学习

    https://www.cnblogs.com/edisonchou/p/3843287.html PDF https://files.cnblogs.com/files/netlock/%E6%95 ...

  10. Autofac官方文档翻译--一、注册组件--2传递注册参数

    官方文档:http://docs.autofac.org/en/latest/register/parameters.html 二.Autofac 传递注册参数 当你注册组件时能够提供一组参数,可以在 ...