当前位置: 首页 > news >正文

线程池——线程池

目录

1.线程池的引入

1.1 理解线程池

1.2 为什么需要线程池?

1.3 为什么直接创建线程开销比从池子中取线程的开销大?

2.Java标准库中的线程池

2.1 常见线程池的实现方式

2.2 线程池核心方法 submit

2.3 线程池的简单应用

3. ThreadPoolExecutor

3.1 构造方法

3.2 拒绝策略

3.3 ThreadPoolExecutor 的简单测试

4.实现一个简单的线程池

5.小结


1.线程池的引入

1.1 理解线程池

有了线程,为什么还需要线程池?线程池是干什么的?

我们之所以引入线程,是因为进程的创建和销毁的开销都非常大。在这里也是类似的,现代的生活方方面面都离不开计算机,随着计算机的使用,人们更希望开销更小的线程来执行计算机的工作。

比如在现实生活中,一个快递中转站有 1000 个小包裹,并且都是发往同一个目的地的,驿站会安排 1000 个快递小哥,每个快递小哥送一个包裹吗?显然这是不可能的,而是可能会安排两三名快递小哥去送货。

线程池就是这样的机制。线程池是一种多线程处理形式,它通过预先创建一组线程并重复利用它们来执行任务,而不是每个任务都会创建新的线程

主要注意理解的是:程序运行时线程就已经创建,放在一个池里,如果这些线程等待分配任务就可以立刻执行,而不是任务来了,这些线程才会创建

什么是池?在学习 Java 的 String 类中,我们是接触到池的概念的,那里是池我们说字符串常量是 Java 程序在最初构建的时候,就已经准备好,等到程序运行的时候,这些常量就加载到内存中,剩下的只有构造和销毁的开销,而不必再进行创建

我想大家在现实生活中,应该认识“水池”这个概念,在一些地区,因为地区干旱或者其它原因,可能会导致饮用水紧缺,于是当地的人们就造应该水池来存储水,用通过水管把这些水池中的水通往家家户户,当人们需要用水的时候,在家里一拧阀门就可以用到水,而不用担心水有没有的问题,如果没水,那只能说明是水池干了,然后全村的人会一起想办法解决饮水问题。

在线程池中也是这样的,程序运行后,就创建一些线程放在“池”里,当需要它们的时候,就可以直接使用,而且可以循环利用,这些线程无故的销毁,除非是出现异常或者程序猿手动销毁

1.2 为什么需要线程池?

所以为什么需要线程池?

  • 其一是减少线程的创建降低开销,线程又可以重复使用,节省资源
  • 其二是提高响应速度,因为线程池在程序运行的时候就已经创建,当有任务请求时可以立刻响应
  • 其三是提供拒绝策略,当任务过多时可以采取合理的策略处理(在后文会讲)。

1.3 为什么直接创建线程开销比从池子中取线程的开销大?

同样也是创建线程,为什么直接创建线程的开销会比从池子中取线程的开销大?

  • 是直接创建线程,每次创建都会 new Thread() 并调用 start() 启动,这个时候会给每个线程分配内存,并且还需要资源初始化(比如初始化线程上下文、本地变量等)
  • 线程池通过线程复用避免了重复创建和销毁(一千个快递让一千个快递小哥送货和让两三个快递小哥送货的区别),当任务执行时直接用已经创建的线程或者等待 wait ,等线程执行完前面的任务。并且线程的上下文可以重复利用

2.Java标准库中的线程池

2.1 常见线程池的实现方式

Java标准库中线程池有很多,最基础的是 Executor,这是一个接口

它提供四种实现方式,分别是:

  • Executors.newCachedThreadPool(可缓存线程池):创建一个可缓存线程池,线程池长度根据任务需求创建新线程,会无限增加,线程空闲时会被回收
  • Executors.newFixedThreadPool(固定大小线程池):创建一个定长线程池,超出的线程会在队列中等待,线程空闲不会被回收
  • Executors.newScheduledThreadPool(定时任务线程池): 创建一个定时线程池,支持定时及周期性任务执行,线程空闲不会被回收
  • Executors.newSingleThreadExecutor(单线程线程池):创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

其它的线程池:

  • ExecutorService: 扩展了Executor,增加了生命周期管理方法
  • ThreadPoolExecutor: 最灵活的线程池实现
  • ScheduledThreadPoolExecutor: 支持定时/周期性任务的线程池

这些线程池也可以实现以上四种方式。

在这里主要介绍的就是 ThreadPoolExecutor,这是最灵活的线程池实现,它可以允许自定义,自定义的包括:核心线程数,最大线程数,空闲线程存活时间,时间,任务队列,拒绝策略,这在后文会重点讲解。

2.2 线程池核心方法 submit

submit(Runnable) 方法是线程池的核心方法:通过 Runnable 描述一段要执行的任务,通过 submit 把任务提交到线程中,此时线程池里的线程就会来执行这个任务。(当然需要注意,往线程里添加任务的方法还可以是 execute,但它不支持返回值所以我们一般用 submit)这是线程池的核心方法,后续简单实现一个线程池时,就需要实现这个方法。

线程池的关闭方法是 shutdown()。这个方法是当我们在结束程序后,期望线程池也关闭的时候使用。

2.3 线程池的简单应用

public class Test {public static void main(String[] args) {// 创建一个固定大小为10的线程池ExecutorService executorService = Executors.newFixedThreadPool(10);// 提交100个任务到线程池for (int i = 0; i < 100; i++) {final int taskId = i; // 必须是final或等效final// 显式创建Runnable对象Runnable task = new Runnable() {@Overridepublic void run() {try {System.out.println("线程" + Thread.currentThread().getName()+ " 正在执行任务" + taskId);Thread.sleep(1000); // 模拟任务执行时间} catch (InterruptedException e) {throw new RuntimeException(e);}}};// 提交Runnable任务executorService.submit(task);}// 关闭线程池(实际开发中应该用try-finally确保关闭)executorService.shutdown();}
}

代码解读:创建一个固定大小为10的线程池,这里用 newFixedThreadPool 实现,打算生产100个任务,通过 Runnable 描述这个任务然后调用 submit 提交任务

结果分析:只有一个线程池,pool-1,然后创建了10个线程池,线程随机调度、抢占式执行,提交的任务被不同的线程来完成。

3. ThreadPoolExecutor

ThreadPoolExecutor 是Java标准库中提高的线程池中最灵活的一个,除了可以实现常见 4 实现之外,它还有自己的构造方法:

构造方法主要有三种,而且参数都很多,最多的参数高达 7 个,所以在这里着重理解这些参数都是干什么的。

3.1 构造方法

第 1 个参数:int corePoolSize:核心线程数,表示至少有几个线程这些核心线程随着线程池创建的时候就跟着创建,知道整个线程池销毁,这些线程才会销毁,作用是线程池中保持存活的最小线程数量,即使它们处于空闲状态。

第 2 个参数:int maximumPoolSize:最大线程数最大线程数 = 核心线程数 + 非核心线程数,当工作队列满时,线程池会创建新线程,直到达到这个最大线程数

第 3 个参数:long keepAliveTime:允许非核心线程空闲的最大时间当线程空闲时间超过这个最大值且当前线程数大于 corePoolSize 时,线程会被回收,单位由下一个参数 unit决定

第 4 个参数:TimeUnit unit:上一个参数 keepAliveTime 的时间单位,TimeUnit 是一个枚举类型,其时间单位有以下几种:

  • TimeUnit.NANOSECONDS

  • TimeUnit.MICROSECONDS

  • TimeUnit.MILLISECONDS

  • TimeUnit.SECONDS

  • TimeUnit.MINUTES

  • TimeUnit.HOURS

  • TimeUnit.DAYS

第 5 个参数:BlockingQueue<Runnable> workQueue:工作队列,本质上是生产者-消费者模型,用于保存等待执行的任务的阻塞队列可以指定容量 capacity指定是否要带有优先级/比较规则(优先级队列),传入的是 Runnable,调用 submit 方法时,传入任务,然后线程池里的线程消费任务

第 6 个参数:ThreadFactory threadFactory:线程工厂用于创建新线程的工厂,可以自定义线程名称、优先级、是否为守护线程等默认使用 Executors.defaultThreadFactory(),会默认创建命名为"pool-N-thread-M"的普通优先级非守护线程(如在上面演示线程池的简单使用)

工厂模式:

工厂模式(Factory Pattern)也是Java中最常用的设计模式之一,它的核心原则是:定义一个创建对象的接口,但让子类决定实例化哪个类。工厂方法让类的实例化延迟到子类进行。当线程池需要新线程时,不是直接new Thread(),而是通过这个工厂来创建。工厂模式可以简单理解为它是用来弥补构造方法的缺陷,但是它的功能不止于此。关于工厂模式在后面会讲到。

第 7 个参数RejectedExecutionHandler handler拒绝策略:当线程池和工作队列都饱和时采取的拒绝策略,比如调用 submit 把任务添加到任务队列中,因为任务队列是阻塞队列,如果队列满了,再进行添加,就会阻塞,但是一般在实际开发中并不希望程序阻塞太久,所以对于线程来说,发现队列满之后,不会真的触发入队等待操作,而是采取一定的策略解决。拒绝策略有四种,下面讲解:

3.2 拒绝策略

当线程池和工作队列都饱和时采取的措施叫拒绝策略。

比如,张三周末的时间安排是:上午在家里睡个懒觉并打扫房间卫生,下午出去健身,打造完美身材,晚上和好朋友一起连麦开黑。但是在周六这天,张三曾经暗恋的女神给他发消息说周日晚上想和张三一起逛街看电影。张三一来觉得自己的女神平时对自己爱搭不理,二来觉得这是自己曾经的女神,导致张三犹豫不决,于是张三做出了四种选择:

(1)AbortPolicy(默认):抛出 RejectedExecutionException

张三觉得因为事先答应了自己的好哥们要一起连麦开黑,这是他最铁的哥们之一,所以张三直接对女神的邀约抛出“异常”,不搭理自己的女神。

(2)CallerRunsPolicy:由提交任务的线程自己执行该任务

张三觉得你不过就是我曾经的女神,你逛街没人陪吗?为什么要喊我?你自己逛街去,不要打扰我......所以张三就让自己曾经的女神一个人去逛街了。

(3)DiscardPolicy:直接丢弃任务,不做任何处理

张三觉得这是自己的女神,但自己确实有事情安排了,所以明确地拒绝女神的邀约女神礼貌并说明有事情要忙,女神觉得张三的拒绝很明确,理解张三,因此女神也不去逛街了。

(4)DiscardOldestPolicy:丢弃队列中最旧的任务,然后尝试提交新任务

张三觉得自己的女神好不容易邀约自己,怎么能有不去的道理的呢?于是跟好哥们说临时有安排了,不能和他连麦开黑了,并同意女神的邀约。

构造方法看起来很复杂,是的,确实很复杂,这里给出一个简单的测试案例:

3.3 ThreadPoolExecutor 的简单测试

public class ThreadPoolExample {public static void main(String[] args) {// 直接创建线程池ThreadPoolExecutor executor = new ThreadPoolExecutor(2,       // corePoolSize(核心线程数)4,       // maximumPoolSize(最大线程数)60L,     // keepAliveTime(空闲线程存活时间)TimeUnit.SECONDS,  // 时间单位new ArrayBlockingQueue<>(2),  // 任务队列(容量=2)Executors.defaultThreadFactory(),  // 线程工厂new ThreadPoolExecutor.AbortPolicy()  // 拒绝策略 -- 抛出异常);// 提交任务for (int i = 1; i <= 10; i++) {final int taskId = i;try {//提交任务,这里用的是 lambda 表达式,所以没有看到 Runnable 任务executor.submit(() -> {System.out.println("线程 " + Thread.currentThread().getName() + " 正在执行任务 " + taskId);try {Thread.sleep(1000);  // 模拟任务执行} catch (InterruptedException e) {e.printStackTrace();}});} catch (RejectedExecutionException e) {System.err.println("任务 " + taskId + " 被拒绝!");}}// 关闭线程池executor.shutdown();}
}

相关代码解读在注释中,运行结果:

结果解读:结果显示打印 4 个任务,但是我们的代码中,应该可以容纳的最大容量 = 最大线程数 4 + 队列容量 2 = 6,从打印结果中来看,是线程1执行任务1,线程2执行任务2,线程3执行任务5,线程4执行任务6,定义的最大线程数是4,所以任务3和任务4在队列里等待(此时已经达到最大容量 6),因为执行任务会睡眠1秒,赶不上任务生产的速度,所以当任务 7,8,9,10 加入时,就超出了最大容量 6,而我们给定的拒绝策略是抛出异常,所以加下来生产的任务都被拒绝,当生产完毕后,不再进行生产(代码里的 for 循环只定义了 10 个任务)。当线程执行完应该任务后,继续执行队列里正在等待的任务,就可以看到了线程3执行任务3,线程4执行任务4。哪个线程执行哪个任务是不固定的,因为线程随机调度、抢占式执行!

(其它的拒绝策略可以自己尝试哦)

ThreadPoolExecutor 线程池构造方法看似很难,其实一点也不简单。简单的可以用 ExecutorService threadPool = Executors.newFixedThreadPool(10) 类似这种方式定义一个线程池(在2.1里有讲)。不过再难也是要理解掌握的,因为在一些公司会要求使用 ThreadPoolExecutor 这个版本,因为参数参数多,可以自己控制,可以根据需求定义。

4.实现一个简单的线程池

实现一个简单的线程池就是实现它怎么创建线程,怎么把任务加到线程中就行,如果真让我们实现一个带有七个参数的版本,对于目前小白的我们来讲那也不现实。

/*** 自定义简单线程池实现* 1. 使用阻塞队列管理待执行任务* 2. 固定数量的工作线程* 3. 支持任务提交和执行*/
public class MyThreadPool {// 任务队列,用于存放待执行的任务private BlockingQueue<Runnable> queue = null;/*** 构造函数初始化线程池* @param capacity 线程池中工作线程的数量*/public MyThreadPool(int capacity) {// 初始化任务队列,容量固定为100queue = new ArrayBlockingQueue<>(100);// 创建并启动工作线程for (int i = 0; i < capacity; i++) {// 每个工作线程都是一个无限循环的线程Thread t = new Thread(() -> {try {while (true) {  // 线程持续运行// 从队列中取出任务(如果队列为空会阻塞)Runnable task = queue.take();// 执行任务task.run();}} catch (InterruptedException e) {// 线程被中断时的处理e.printStackTrace();}});t.start();  // 启动工作线程}}/*** 提交任务到线程池* @param task 待执行的任务* @throws InterruptedException 当队列已满且被中断时抛出*/public void submit(Runnable task) throws InterruptedException {// 将任务放入队列(如果队列已满会阻塞)queue.put(task);}
}

线程池的核心方法是 submit,这个方法是往线程中添加任务,添加任务后,需要有线程来执行这个任务,所以就会调用这个 submit 方法;在构造方法中,需要把线程创建出来;用 while 判断死循环,是因为随时都有可能产生新的任务被添加到线程中,所以要一直尝试读取,如果读取到任务执行,没有读取到就不执行。

5.小结

本期主要讲线程池,解决了为什么引入线程池,以及线程池的开销为什么比直接创建线程低,并了解Java标准库中的线程池,还深入理解 ThreadPoolExecutor 的七个参数分别是什么,最后实现一个简单的线程池。

http://www.dtcms.com/a/449612.html

相关文章:

  • WebSocket细谈
  • 公司网站怎么建站微网站如何做微信支付宝支付
  • Ubuntu 原地升级 MongoDB 全攻略
  • 网站变灰色代码安徽省建设工程信息网官网是什么网站
  • Hexo博客搭建系列(四):透明居中导航栏+鼠标悬停放大效果
  • 【STM32项目开源】基于STM32的智能仓库火灾检测系统
  • 陕西省建设监理协会网站证书wordpress 图片外链
  • 做模板网站企业网站类型
  • 24H2壁纸显示错误修复(针对vb.net的紧急加更)
  • 兰州做网站 东方商易怎么样做美术招生信息网站
  • 酒店客房管理系统|基于SpringBoot和Vue的酒店客房管理系统(源码+数据库+文档)
  • AI编程开发系统019-基于Vue+SpringBoot的邮件收发系统(源码+部署说明+演示视频+源码介绍+lw)
  • 做海免费素材网站排版设计模板
  • 212-基于Python的老人健康管理系统
  • 万能格式文件查看工具,支持查看图像、音视频和文档等,免安装超方便!
  • 做食品企业网站的费用wordpress文章图片全屏浏览
  • 韩国免费行情网站的推荐理由外贸 wordpress
  • 嵌入式开发核心知识点详解教程
  • 操作系统应用开发(二十六)RustDesk tls证书不匹配错误—东方仙盟筑基期
  • 如何制作个人网站住建局查询房产信息
  • 西安网站建设hyk123wordpress帖子添加代码
  • 乐理知识学习内容
  • 新手SEO教程:高效提升网站访问量的实用技巧与策略
  • 代码文件内容
  • 一款基于ESP32的导航小车
  • 自己建设网站赚钱湘潭网站建设 要选磐石网络
  • Python图形界面——TKinter
  • 深圳策划公司网站建设大型网站制作品牌
  • Django 配置与安装完整指南
  • seo网站优化方法网站建设技术指标