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

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

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

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

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

执行器线程池方法


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

二、线程池示例

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

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

  • 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑
  • 使用Executors创建线程池ExecutorService
  • 将待执行的任务对象交给ExecutorService进行任务处理
  • 停掉 Executor 线程池
//第一步: 创建一个任务对象(实现Runnable接口),用于执行具体的任务逻辑 (Step 1)
class Task implements Runnable {private String name;public Task(String s) {name = s;}// 打印任务名称并Sleep 1秒// 整个处理流程执行5次public void run() {try{for (int i = 0; i<=5; i++) {if (i==0) {Date d = new Date();SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");System.out.println("任务初始化" + name +" = " + ft.format(d));//第一次执行的时候,打印每一个任务的名称及初始化的时间}else{Date d = new Date();SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");System.out.println("任务正在执行" + name +" = " + ft.format(d));// 打印每一个任务处理的执行时间}Thread.sleep(1000);}System.out.println("任务执行完成" + name);} catch(InterruptedException e) {e.printStackTrace();}}
}

测试用例

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

示例执行结果

任务初始化task 1 = 05:25:55
任务初始化task 2 = 05:25:55
任务初始化task 3 = 05:25:55
任务正在执行task 3 = 05:25:56
任务正在执行task 1 = 05:25:56
任务正在执行task 2 = 05:25:56
任务正在执行task 1 = 05:25:57
任务正在执行task 3 = 05:25:57
任务正在执行task 2 = 05:25:57
任务正在执行task 3 = 05:25:58
任务正在执行task 1 = 05:25:58
任务正在执行task 2 = 05:25:58
任务正在执行task 2 = 05:25:59
任务正在执行task 3 = 05:25:59
任务正在执行task 1 = 05:25:59
任务正在执行task 1 = 05:26:00
任务正在执行task 2 = 05:26:00
任务正在执行task 3 = 05:26:00
任务执行完成task 3
任务执行完成task 2
任务执行完成task 1
任务初始化task 5 = 05:26:01
任务初始化task 4 = 05:26:01
任务正在执行task 4 = 05:26:02
任务正在执行task 5 = 05:26:02
任务正在执行task 4 = 05:26:03
任务正在执行task 5 = 05:26:03
任务正在执行task 5 = 05:26:04
任务正在执行task 4 = 05:26:04
任务正在执行task 4 = 05:26:05
任务正在执行task 5 = 05:26:05
任务正在执行task 4 = 05:26:06
任务正在执行task 5 = 05:26:06
任务执行完成task 4
任务执行完成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)会实现最高效率。

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

最新2021整理收集的一些高频面试题(都整理成文档),有很多干货,包含mysql,netty,spring,线程,spring cloud、jvm、源码、算法等详细讲解,也有详细的学习规划图,面试题整理等,需要获取这些内容的朋友请加Q君样:11604713672

Java 线程池的作用以及该如何使用相关推荐

  1. java线程池的作用

    现在服务器端的应用程序几乎都采用了"线程池"技术,这主要是为了提高系统效率.因为如果服务器对应每一个请求就创建一个线程的话,在很短的一段时间内就会产生很多创建和销毁线程动作,导致服 ...

  2. Kafka必须掌握的核心技术:简述Java线程池的作用和实现方式

    Part 1消息队列 介绍消息队列技术的背景,包括使用场景和消息队列的功能特点,并设计了一个简单的消息队列. 1.1 系统间通信技术介绍 1.2 为何要用消息队列 1.3 消息队列的功能特点 1.4 ...

  3. java线程池有什么作用_java线程池的作用是什么?线程池介绍

    你知道java中线程池的作用是什么吗?那么究竟什么是线程池呢?都有哪些类型呢?让我们对以上的问题来进行详细的了解吧. 一.java线程池作用 第一个我们先来对它的作用进行一下简单的介绍,使用线程池的优 ...

  4. Java线程池有哪些作用

    线程池 线程池的作用 核心点:复用机制提前创建好固定的线程一直在运行状态实现复用限制线程创建数量. 1.降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗. 2.提高响应速度 ...

  5. 四种Java线程池用法解析

    四种Java线程池用法解析 本文为大家分析四种Java线程池用法,供大家参考,具体内容如下 http://www.jb51.net/article/81843.htm 1.new Thread的弊端 ...

  6. java线程池的使用例子,不愧是大佬

    京东Java研发岗一面(基础面,约1小时) 自我介绍,主要讲讲做了什么和擅长什么 springmvc和spring-boot区别 @Autowired的实现原理 Bean的默认作用范围是什么?其他的作 ...

  7. 面试题:四种Java线程池用法解析 !=!=未看

    1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? 1 2 3 4 5 6 7 8 new Thread(new Runnable() {   @Override   ...

  8. java 线程池ThreadPoolExecutor

    线程池 线程池的作用: 线程池作用就是限制系统中执行线程的数量. 根 据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果:少了浪费了系统资源,多了造成系统拥挤效率不高.用线程池控制线程数 ...

  9. 由浅入深理解Java线程池及线程池的如何使用

    前言 多线程的异步执行方式,虽然能够最大限度发挥多核计算机的计算能力,但是如果不加控制,反而会对系统造成负担.线程本身也要占用内存空间,大量的线程会占用内存资源并且可能会导致Out of Memory ...

最新文章

  1. poj 2109 Power of Cryptography
  2. .在OnPaint()函数和在OnEraseBkgnd()重绘图的区别
  3. JavaScript编程艺术-第7章代码汇总(2)
  4. java iecapt.exe_java替换url的域名和端口方法
  5. 如何画正太分布曲线_python scipy.stats实现各种常见的统计分布
  6. 解析bt种子下载 java_使用Java解析Torrent文件(BT种子),基于使用Eclipse ECF中的org.eclipse.bittorrent方案...
  7. 测试开发工程师简历模版
  8. Java NIO、BIO介绍
  9. C语言中的 pow 函数 使用方法及注意事项,和常见报错原因,且分享实战中的使用
  10. C语言字谜游戏(函数嵌套、指针)
  11. 汉字转为16进制的unicode
  12. themeforest 模板
  13. mysql 按照条件计数_Mysql按条件计数的几种方法
  14. 工欲善其事,必先利其器
  15. 摄影测量中的什么是光流场_摄影中的“停止”是什么?
  16. 对token(令牌)的理解
  17. Ubuntu设置1080分辨率
  18. github项目推荐:少儿图形化编程启蒙游戏
  19. 使用openocd调试Linux内核,OpenOCD-JTAG调试(示例代码)
  20. itoa()和atoi()函数详解

热门文章

  1. Python学习进度内容表
  2. 开发者续费:没有足够信息以重设您的安全提示问题
  3. 快速了解什么是“云打印”
  4. Springboot+Thymeleaf中常用的th标签
  5. thymeleaf的文本内联th:inline
  6. Mac更新10.14后频繁重启
  7. SSH服务器跳转失败
  8. rs232读取智能电表_让生活更智能,十款智能电表给你充充电
  9. The Sandbox 与 FaZe Clan 达成合作,将 “FaZe World” 带入现实世界!
  10. 关于 此证书的签发者无效 解决办法 (整理自 网络)