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

JAVA中的线程池说明一

目录

1.为什么需要线程池?

2.什么是线程池?

3.标准库中的线程池

4.实现自定义线程池


1.为什么需要线程池?

       线程的存在意义在于解决并发编程中进程开销过大的问题,因此引入了线程,也被称为"轻量级线程"。相比于创建进程,创建线程更加高效;同样地,销毁线程比销毁进程更高效,调度线程比调度进程更高效。

       在许多情况下,使用多线程可以替代进程来实现并发编程。然而,随着并发程度的提高和对性能要求的提高,频繁创建和销毁线程会导致线程创建变得不那么轻量。为了降低创建和销毁线程的开销,我们可以使用线程池来管理线程资源。

2.什么是线程池?

       线程池是一种管理线程的机制,它预先创建一定数量的线程,并将它们放入一个池中。当需要执行任务时,线程池会从池中选择一个空闲的线程来执行任务,而不是每次都创建新的线程。一旦任务完成,线程会被归还到线程池中,等待下一次使用。

       在之前我们也了解过,相关的池化技术,字符串常量池,数据库连接池,HTTP连接池等,池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。

线程池的主要优点包括:

  • 提高性能,降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程池可以限制同时运行的线程数量,避免过多的线程导致系统资源耗尽,使用线程池可以进行统一的分配,调优和监控。

上面我们说到:

将线程从线程池里获取,用完后还给线程池,这两个动作比创建/销毁进程更高效

那这里的原因是什么呢?

       是因为创建/销毁进程是交由操作系统内核完成的,从线程从线程池里获取,用完后还给线程池,是我们自己用户的代码就可以实现的,不必交给内核操作。

那什么是操作系统内核呢?

       操作系统内核是操作系统的核心部分,它负责管理系统的资源、调度进程和线程的执行、处理硬件设备等。它是操作系统与计算机硬件之间的接口,提供了系统级别的服务和功能。

       比如在银行大厅里,用户们可以自由地操作,这就像计算机程序中的"用户态"。在用户态下,执行的是程序员编写的代码,用户可以按照自己的意愿进行各种操作,完全由程序员来决定。

       然而,有些特定的任务必须在银行柜台内完成,客户无法直接进入柜台,必须通过银行工作人员来执行这些任务。这与计算机程序中的"内核态"相似。内核态的操作都在操作系统的内核中进行,内核提供给程序员一些应用程序接口(API),称为系统调用。程序员可以通过调用这些API来驱动内核完成某些工作。系统调用的内容直接与内核的代码相关,这部分代码不受程序员的控制,完全由内核自行处理。

       与内核相比,用户态的程序行为是可控的。如果用户想要立即完成某项工作,可以直接在用户态下进行。但是,如果想要通过内核从系统中创建一个新的线程,就需要使用系统调用,让内核来执行这一操作。然而,我们无法确定内核当前正在处理多少其他任务。例如,如果你希望柜台工作人员帮你存钱,但前面可能有很多人正在排队等待服务。因此,当我们使用系统调用来执行内核代码时,无法确切知道内核将执行哪些具体操作,这使得整个过程相对"不可控"。

3.标准库中的线程池

       在Java标准库中,也有现成的线程池,可以直接进行使用,下面这个代码是指,创建一个线程池,池子里现成数目固定为10个。

    public static void main(String[] args) {//创建一个线程池,池子里现成数目固定为10个ExecutorService pool = Executors.newFixedThreadPool(10);for (int i = 0; i < 1000; i++) {int n = i;pool.submit(new Runnable() {@Overridepublic void run() {System.out.println("hello" + n);}});}}

ExecutorService pool = Executors.newFixedThreadPool(10);

       在这个例子中,我们使用了`newFixedThreadPool()`方法创建了一个固定大小的线程池。这种设计模式被称为"工厂模式"。

       线程池提供了一个重要的方法`submit`,可以用来向线程池提交多个任务。当我们运行这段代码时,会发现主线程已经结束,但是线程池中的线程仍然在运行,这是因为这些线程都是前台线程,它们会阻止线程池的关闭。

       需要注意的是,这里我们向线程池提交了1000个任务,这些任务将被10个线程平均分配。每个线程大约执行100个任务,但由于每个任务执行时间相近,因此每个线程处理的任务数量也大致相同。可以认为这1000个任务被放入一个队列中,10个线程依次从队列中取出任务并执行。

int n = i;

       这里需要再定义一个变量n,而不是直接使用i的原因是变量捕获。

       在主线程中,i是一个局部变量,当主线程的for循环结束时,i将被销毁。然而,有可能主线程的for循环结束,但当前的run任务尚未在线程池中执行。

       为了避免作用域差异导致后续执行run方法时i已经销毁,我们需要进行变量捕获,即将主线程中的i值传递给当前run方法所在的线程。这样,即使主线程的i被销毁,run方法仍然可以访问到正确的值。

       在Java中,Lambda表达式和匿名内部类都可以捕获外部作用域中的变量。对于JDK 1.8之前的版本,只能捕获被final修饰的局部变量或者实例变量。这是因为这些变量一旦初始化后就不能改变,所以它们可以在Lambda表达式或匿名内部类中使用而不会引发线程安全问题。

       然而,从JDK 1.8开始,Java引入了一个新的特性叫做"effectively final"。这个特性允许我们在Lambda表达式或匿名内部类中捕获那些实际上没有被修改过的变量。换句话说,即使变量没有被明确地声明为final,只要它在Lambda表达式或匿名内部类中没有被修改过,就可以被捕获。

4.实现自定义线程池

这里的实现自定义线程池比较简单,不多过于赘述。

class MyThreadPool {private BlockingQueue<Runnable> queue = new LinkedBlockingDeque<>();//n表示线程数量public MyThreadPool(int n) {//在这里创建线程for (int i = 0; i < n; i++) {Thread t = new Thread(() -> {while (true) {try {Runnable runnable = queue.take();runnable.run();} catch (InterruptedException e) {e.printStackTrace();}}});t.start();}}//注册任务给线程池public void submit(Runnable runnable) {try {queue.put(runnable);} catch (InterruptedException e) {e.printStackTrace();}}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C++ | Leetcode C++题解之第377题组合总和IV
  • 上书房信息咨询:生活投诉满意度调研
  • PyTorch深度学习模型训练流程:(一、分类)
  • Debezium系列之:记录一次命令行可以访问mysql数据库,但是debezium connector无法访问数据库原因排查
  • 掌控安全CTF-2024年8月擂台赛-ez_misc
  • XSS - LABS —— 靶场笔记合集
  • Sentinel组件详解:使用与原理
  • 进程间通信:采用有名管道,创建两个发送接收端,父进程写入管道1和管道2,子进程读取管道2和管道1.
  • Python中的赋值运算符:解锁编程的无限可能
  • 加速打开gtihub的工具dev-sidecar
  • 急急急!苹果手机突然黑屏无法开机怎么办?能解决吗?
  • PHPShort轻量级网址缩短程序源码开心版,内含汉化包
  • 微软Win11 22H2/23H2八月可选更新KB5041587发布!
  • Element-plus组件库基础组件使用
  • 如祺出行发布首份中期业绩,总收入增长13.6%
  • 【Leetcode】101. 对称二叉树
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 【跃迁之路】【444天】程序员高效学习方法论探索系列(实验阶段201-2018.04.25)...
  • Angular Elements 及其运作原理
  • css的样式优先级
  • Git 使用集
  • Java 内存分配及垃圾回收机制初探
  • JavaScript新鲜事·第5期
  • Java读取Properties文件的六种方法
  • maya建模与骨骼动画快速实现人工鱼
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • ucore操作系统实验笔记 - 重新理解中断
  • 阿里云Kubernetes容器服务上体验Knative
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 回流、重绘及其优化
  • 讲清楚之javascript作用域
  • 微服务核心架构梳理
  • 微信支付JSAPI,实测!终极方案
  • 我有几个粽子,和一个故事
  • 学习笔记:对象,原型和继承(1)
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • ​flutter 代码混淆
  • #如何使用 Qt 5.6 在 Android 上启用 NFC
  • (06)Hive——正则表达式
  • (1)Hilt的基本概念和使用
  • (6)添加vue-cookie
  • (rabbitmq的高级特性)消息可靠性
  • (二刷)代码随想录第16天|104.二叉树的最大深度 559.n叉树的最大深度● 111.二叉树的最小深度● 222.完全二叉树的节点个数
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)ssm考生评分系统 毕业设计 071114
  • (附源码)小程序 交通违法举报系统 毕业设计 242045
  • (三)uboot源码分析
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (学习日记)2024.01.09
  • (循环依赖问题)学习spring的第九天
  • (一)kafka实战——kafka源码编译启动
  • (一)Linux+Windows下安装ffmpeg
  • (杂交版)植物大战僵尸
  • (转)VC++中ondraw在什么时候调用的