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

[线程池]

我们最初引入"线程"这个概念, 就是因为进程太"重量"了, 平凡的创建和销毁线程需要的开销很大, 所以引入一个更轻量级的概念 --> "线程". 线程可以看做是轻量级的"进程", 创建和销毁的开销很小. 引入"线程"这个概念, 大大提高了我们程序的性能.  然而, 时代发展飞快, 随着业务的不断复杂化和业务对性能要求的提升, 线程创建销毁带来的开销, 变得越来越明显(不再可以忽略了).

所以此时我们就引入了"线程池"来解决上述问题. "线程池" 就是把多个线程提前从系统申请好, 放到一个地方, 需要用线程的时候, 直接从这里取, 不用再去系统申请线程了, 之后当线程使用完之后, 还是放回到这个地方. 

线程池这样的做法, 可以看做是把要用的资源"提前准备好", 然后等到需要的时候, 直接去拿. 这样就避免了"用一次创建一次"这样繁琐的步骤. (一次性创建好所有资源当然比一次一次的创建开销更小).

[理解]: 为什么从线程池里直接取线程, 比去系统申请线程的开销更小, 更加高效呢?  --> 这里就涉及到"用户态"和"内核态"了. 我们直接从线程池里拿线程, 这个操作时纯纯的用户态代码, 根内核一点关系也没有 (因为线程都已经提前申请好了). 但是我们如果要去系统内核申请一个线程的话, 这个过程就变得"不可控"了, 谁知道内核会先去做什么, 做完之后才会去给你创建线程. 所以把这个任务交给内核, 内核创建线程的这个过程是"不可控"的. 所以, 这就是线程池为什么更高效的原因.

([注]: 我们通常认为, 纯用户态的操作, 就是比经过内核处理的操作 更加高效).

java标准库提供了现成的线程池来供我们使用. java.util.concurrent 包(简称juc包), 提供了很多有关多线程的API. 我们现在要提到的线程池的构造方法 ThreadPoolExecutor 就在这个包里面.

ThreadPoolExecutor是线程池的构造方法, 这个构造方法里面有很多参数. 每个参数都代表什么含义, 这是我们需要取重点理解的.

 我们可以看到, ThreadPoolExecutor 这个类一共有7个参数, 接下来我们就看看这几个参数分别是干什么用的吧:

(1) int corePoolSize, int maximumPoolSize

corePoolSize -> 核心线程数,  maximumPoolSize -> 最大线程数. 线程池, 一般都支持"线程扩容", 比如某线程池一开始有M个线程, 但是, 后来发现在实际使用中, M个线程不太够用, 所以就会对线程池进行扩容, 增加线程的个数. 这里的corePoolSize(核心线程数)可以理解为该线程池最少有多少个线程,  而线程池中, 除了有核心线程, 还有非核心线程 (在线程池扩容过程中增加的). maximumPoolSize 就可以理解为"最大线程数" (核心线程数 + 非核心线程数 的最大值).

(2) long keepAliveTime, TimeUnit unit

非核心线程在其空闲的时候就会被销毁, 这里的keepAliveTime就表示允许非核心线程空闲的最大时间, 如果某个非核心线程空闲时间超过了这一数值, 那么这个线程将被销毁. 后面的TImeUnit unit 表示空闲时间的单位, 有 秒, 分钟, 小时, 天 .

(3) BlockingQueue<Runnable> workQueue

该参数表示线程池的工作队列. 线程池的工作过程是典型的"生产者--消费者"模型. 我们在使用线程池的时候, 需要通过"submit"方法, 把要执行的任务提交给工作队列. 这里我们在参数中看到的"Runnable"就是要执行的任务. (Runnable这个接口本身的含义就是一段可执行的任务).

(4) ThreadFactory threadFactory

"线程工厂" , ("工厂"指的是"工厂设计模式") ThreadFactory这个类是线程的工厂类, 用于完成Thread的实例创建和初始化操作. 而起ThreadFactory可以对线程池中的多个线程进行批量的设置属性. (注: 这里我们一般不会做出调整, 使用ThreadFactory的默认值即可).

(5) RejectedExecutionHandler handler

这个参数的意思是 "拒绝策略". 这个参数是7个参数中最为复杂的一个, 也是最为重要的一个. 这个参数表示: 当线程池的工作队列满了之后, 如果还要继续给这个队列添加任务, 此时线程池应该怎样做? --> 拒绝!!!   那么怎样拒绝呢? java标准库给出了四种不同的拒绝策略.

 如上图, java一共提供了4种不同的拒绝策略:

(1) AbortPolicy

        添加任务的时候, 直接抛出异常.

(2) CallerRunsPolicy

        线程池拒绝执行该任务, 让submit该任务的线程负责执行.

(3) DiscardOldestPolicy

        把工作队列中最老的任务删除掉, 然后执行新添加的任务.

(4) DiscardPloicy

        把工作队列中最新的任务删除掉.

虽然 ThreadPoolExecutor 的功能很强大, 但是由于参数太多, 使用起来十分的麻烦. 所以java标准库对这个类做了进一步封装 --> Executors. Executors提供了一些工厂方法, 可以更方便地构造出线程池.

上述这些方法都是对ThreadPoolExecutor的进一步封装.

其中, newCachedThreadPool() 设置了非常大的线程数, 使得我们可以对线程池进行不停的扩容;  newFixedThreadPool() 把核心线程数和最大线程数设置成了一样的值, 那么此时线程池的线程数就变成了固定数量, 不会自动扩容.

在创建线程池的时候, 需要指定线程的个数, 那么该如何确定最合适的线程个数的值呢? --> 通过实验的方式. 通过实验测定不同线程数下程序的性能, 以确定最合适的线程个数.

  好了, 本篇文章就介绍到这里啦, 大家如果有疑问欢迎评论, 如果喜欢小编的文章, 记得点赞收藏~~

 


http://www.mrgr.cn/news/70677.html

相关文章:

  • 前端基础的讲解-JS(10)
  • WebStorm 如何调试 Vue 项目
  • Oracle 外键
  • [CKS] K8S AppArmor Set Up
  • uni-app表格带分页,后端处理过每页显示多少条
  • ReactPress:功能全面的开源发布平台
  • day62 53.寻宝
  • 【编程概念基础知识】
  • 【数据结构】图的应用的时间复杂度
  • ‌MySQL 5.7和8.0版本在多个方面存在显著区别,主要包括性能优化、新特性引入以及安全性提升
  • 【FF++】FaceForensics++: Learning to Detect Manipulated Facial Images
  • SpringCloud微服务聚合工程创建指南
  • 明日周刊-第27期
  • [CUDA] cuda程序编译注意事项
  • 解码潜意识:如何用Python构建梦境分析模型
  • C#入门 020 事件(类型成员)
  • (05/16) - 萨班斯-奥克斯利法案(SOX)--- 详解SOX法案
  • 【uiautomator】自动化测试camera【一】
  • 简述 synchronized 和 java.util.concurrent.locks.Lock 的异同?
  • Scrapy搭配Selenium爬取豆瓣电影250排行榜动态网页数据
  • Linux中线程的基本概念与线程控制
  • 深⼊理解指针(5)[回调函数、qsort相关知识(qsort可用于各种类型变量的排序)】
  • YOLOv11融合CVPR[2020]自校准卷积SCConv模块及相关改进思路|YOLO改进最简教程
  • 前端知识点---字符串的8种拼接方法(Javascript)
  • 边缘检测的100种方法
  • PCL 点云拟合 Ransac拟合空间球体