并发编程系列之自定义线程池

2023-11-20

前言

前面我们在讲并发工具类的时候,多次提到线程池,今天我们就来走进线程池的旅地,首先我们先不讲线程池框架Executors,我们今天先来介绍如何自己定义一个线程池,是不是已经迫不及待了,那么就让我们开启今天的旅途吧。

 

什么是线程池?

线程池可以理解为一个专门管理线程生命周期的池子,里面的线程都可以由这个池子本身来调度,使用线程池有哪些好处呢?

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗

  • 提高响应速度:当任务到达时,任务可以不需要的等到线程创建就能立即执行

  • 提高线程的可管理性:使用线程池可以对线程进行统一的分配,调优和监控

 

线程池的实现原理

首先我们看下面这张图,对着图进行分析:

过程分析:当任务到达时,首先会判断核心线程池是否还有空闲线程,如果有则创建一个新的工作线程执行任务,如果没有空闲线程,则说明核心线程池已满,进行工作队列是否满的判断,如果没有满,则将任务存放在等待队列中,如果工作队列也满了,则再去判断线程池是否满,如果没有满,则新建一个线程来执行任务,否则采取拒绝策略;

下面我们来对核心线程池提交任务过程进行分析,这是线程池的核心角色,首先我们看下源码:

public void execute(Runnable command) {
       if (command == null)
           throw new NullPointerException();
       int c = ctl.get();
       // 如果工作线程数量小于核心线程数,则创建一个新的工作线程执行任务
       if (workerCountOf(c) < corePoolSize) {
           // 创建工作线程成功,则直接返回
           if (addWorker(command, true))
               return;
           c = ctl.get();
       }
       // 工作线程>核心线程数或者工作线程创建失败时,将任务放入等待队列中
       if (isRunning(c) && workQueue.offer(command)) {
           int recheck = ctl.get();
           if (! isRunning(recheck) && remove(command))
               reject(command);
           else if (workerCountOf(recheck) == 0)
               addWorker(null, false);
       }
       // 如果等待队列满了,并且工作线程数量大于线程池总数量,则采取拒绝策略
       else if (!addWorker(command, false))
           reject(command);
   }

我们会发现,当可以分配线程来执行任务时,我们总是新建一个工作线程Worker来执行任务,我们来了解下Worker特殊的地方,工作线程不仅会执行当前的任务,而且当前任务执行完毕之后,还会去等待队列中获取任务来执行,通过工作线程的源码可以得知这一点:

final void runWorker(Worker w) {
       Thread wt = Thread.currentThread();
       Runnable task = w.firstTask;
       w.firstTask = null;
       w.unlock(); // allow interrupts
       boolean completedAbruptly = true;
       try {
           while (task != null || (task = getTask()) != null) {
               w.lock();
               if ((runStateAtLeast(ctl.get(), STOP) ||
                    (Thread.interrupted() &&
                     runStateAtLeast(ctl.get(), STOP))) &&
                   !wt.isInterrupted())
                   wt.interrupt();
               try {
                   beforeExecute(wt, task);
                   Throwable thrown = null;
                   try {
                       task.run();
                   } catch (RuntimeException x) {
                       thrown = x; throw x;
                   } catch (Error x) {
                       thrown = x; throw x;
                   } catch (Throwable x) {
                       thrown = x; throw new Error(x);
                   } finally {
                       afterExecute(task, thrown);
                   }
               } finally {
                   task = null;
                   w.completedTasks++;
                   w.unlock();
               }
           }
           completedAbruptly = false;
       } finally {
           processWorkerExit(w, completedAbruptly);
       }
   }

上面我们讲到的工作队列,我们之前学到过队列有很多种,例如阻塞队列和非阻塞队列,有界队列和无界队列,那么在我们线程池中当使用不同的工作工作队列又会有什么区别呢?

  • 使用有界队列时:有新的任务需要执行时,如果线程池实际线程数小于corePoolSize,则优先创建线程,如果大于corePoolSize,则会将任务先加入到队列,等待执行,如果队列也满了,则在总线程数不大于maximumPoolSize时,先创建新的线程,如果线程数大于了maximumPoolSize则执行拒绝策略,或者其他自己自定义的处理策略;

  • 使用无界队列时:LinkedBlockingQueue,与有界队列相比,除非系统资源被耗尽,否则无界队列的任务队列不存在任务入队列失败的情况,当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务,当线程数量达到corePoolSize值后,则线程不会继续新建,如果此时还持续有任务进来,而没有空闲的线程资源,则任务会进入队列排队等待,若任务创建和处理的速度差异很大,则无界队列会保持快速增长,直到资源耗尽内存,任务会一直堆积,直到内存满了,这种情况永远不会有有界队列中工作线程和线程池总数的比较过程;

 

创建线程池:自定义线程池也是通过ThreadPoolExecutor(线程池执行器)来实现,构造方法如下

public ThreadPoolExecutor(int corePoolSize, //核心线程数--线程池初始化创建的线程数量  
                 int maximumPoolSize, // 最大线程数,线程池中能创建的最大线程数
                 long keepAliveTime, // 线程空闲等待时间
                 TimeUnit unit, // 线程空闲等待时间的单位
                 BlockingQueue<Runnable> workQueue, // 存放待执行任务的等待队列
                 RejectedExecutionHandler handler // 拒绝任务的处理策略) {
       this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
            Executors.defaultThreadFactory(), handler);
   }

特别说明,拒绝策略有如下几种:

  • AbortPolicy策略:该策略直接抛出异常,阻止系统工作

  • CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降

  • DiscardOledestPolicy策略:丢弃最老的一个请求任务,也就是丢弃一个即将被执行的任务,并尝试再次提交当前任务

  • DiscardPolicy策略:不处理,直接丢弃掉

 

向线程池提交任务:execute方法在上面已经介绍过了,这里就不重复介绍了

 

关闭线程池:关闭线程池有下面2个方法

// 将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
public void shutdown() {
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           checkShutdownAccess();
           advanceRunState(SHUTDOWN);
           // 调用线程中断方法
           interruptIdleWorkers();
           onShutdown(); // hook for ScheduledThreadPoolExecutor
       } finally {
           mainLock.unlock();
       }
       tryTerminate();
   }

 

// 遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止。
// shutdownNow会首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,
// 并返回等待执行任务的列表,如果任务不一定要执行完,可以使用此方法   
public List<Runnable> shutdownNow() {
       List<Runnable> tasks;
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           checkShutdownAccess();
           advanceRunState(STOP);
           interruptWorkers();
           tasks = drainQueue();
       } finally {
           mainLock.unlock();
       }
       tryTerminate();
       return tasks;
   }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

并发编程系列之自定义线程池 的相关文章

  • java并发总结

    一 并发基础 1 进程与线程 进程 程序由指令和数据组成 但这些指令要运行 数据要读写 就必须将指令加载至 CPU 数据加载至内存 在指令运行过程中还需要用到磁盘 网络等设备 进程就是用来加载指令 管理内存 管理 IO 的 当一个程序被运行
  • 浅析多线程中的各种锁

    高并发的场景下 如果选对了合适的锁 则会大大提高系统的性能 否则性能会降低 所以 知道各种锁的开销 以及应用场景是很有必要的 文章目录 常用的各种锁 互斥锁与自旋锁 互斥锁 自旋锁 读写锁 乐观锁与悲观锁 本文小结 常用的各种锁 多线程访问
  • Java多线程中常见错误梳理,新手程序员必看

    很多Java新手在刚接触线程时都会被其复杂的知识点搞晕 在实际应用中同样错误不断 如何才能快速掌握多线程呢 常见的Java多线程错误有哪些 接下来就给大家分享Java新手学习入门中多线程失误梳理 无论是客户端还是服务器端多线程Java程序
  • 【多线程】三种实现方案

    目录 1 多线程中的并发和并行概念 2 多线程中的进程和线程概念 3 多线程的实现方案 3 1 方式1 继承Thread类的方式进行实现 3 2 方式2 实现Runnable接口 3 3 方式3 Callble和Future 可以获取返回结
  • 并发编程JMM系列之重排序和顺序一致性

    前言 昨天我们接触到了什么是Java内存模型以及两种Java并发模型 并对JMM有了一些初步的认识和了解 我们在上节有提到JMM的重排序规则 但是讲的不详细 今天我们再重点聊下重排序这个东西 以及顺序一致性内存模型 OK 开始我们今天的并发
  • 一次线上的GC问题排查

    6 19号下午 线上系统出现了一次实时链路数据 不通畅的问题 业务方反应更新的增量数据没有流入到HA3搜索集群 登录机器后检查日志后发现 在周六晚上到周天下午 cr search merge 机器人schema统一 表增量数据猛增 初步估计
  • Java并发编程:Copy-On-Write机制详解

    前言 在多线程并发访问共享数据时 可能会出现并发问题导致程序崩溃 数据异常等情况 为了避免这些问题 Java中提供了多种并发控制方法 其中Copy On Write COW 机制就是一种常用的技术 本文将详细介绍COW机制的概念 如何保证线
  • 线程通信基础示例(synchronized 与 Lock + Condition实现线程通信)

    目录 一 synchronized 实现线程通讯 代码示例 二 Lock Condition 实现线程通讯 代码示例 Lock Condition 实现线程通讯的优点 一 synchronized 实现线程通讯 什么是线程通讯 可以将线程分
  • 【2021最新版】Java多线程&并发面试题总结(108道题含答案解析)

    文章目录 JAVA并发知识库 1 Java中实现多线程有几种方法 2 继承Thread类 3 实现Runnable接口 4 ExecutorService Callable Future有返回值线程 5 基于线程池的方式 6 4 种线程池
  • synchronized与(ReentrantLock)Lock的对比区别

    类别 synchronized Lock 存在层次 Java关键字 属于原生语法层面 需要jvm实现 而Lock它是JDK 1 5之后提供的API层面的互斥锁 需要lock 和unlock 方法配合try finally语句块来完成 锁的释
  • Java并发编程的相关知识(7)-阻塞队列

    阻塞队列 ArrayBlockingQueue LinkedBlockingQueue ProiorityBlockingQueue DelayQueue SynchronousQueue LinkedTransferQueue Linke
  • JUC常用到的类

    JUC java util concurrent 并发包中包含了许多并发编程中需要用到的类 锁 如ReentratLock ReadWriteLock ReentrantLock重入锁 可以替代synchronized使用 并且有更多强大的
  • JUC学习笔记及拓展

    本文为自己整理的学习笔记及学习心得 大纲取自尚硅谷的JUC视频 感兴趣的小伙伴可以去B站自学 JUC学习笔记及拓展 Java JUC 1 Java JUC简介 2 volatile 关键字 内存可见性 2 1 内存可见性 2 2 volat
  • Sentinel客户端调用并发控制

    前言 当链路中某个应用出现不稳定 导致整个链路调用变慢 如果不加控制可能导致雪崩 这种情况如何处理呢 一 慢调用现象分析 在分布式链路中调用中 调用关系如下 methodA1与methodA2在同一个应用中 链路标号 调用链 链路1 met
  • Java 线程池的submit的使用与分析.md

    在Java5以后 通过Executor来启动线程比用Thread的start 更好 在新特征中 可以很容易控制线程的启动 执行和关闭过程 还能使用线程池的特性 上一篇我们介绍了线程池的基本用法和特性 我们用的最多的是ExecutorServ
  • Semaphore 源码分析

    需要提前了解的知识点 AbstractQueuedSynchronizer 实现原理 类介绍 Semaphore 信号量 是用来控制同时访问特定资源的线程数量 它通过协调各个线程 以保证合理的使用公共资源 比如控制用户的访问量 同一时刻只允
  • synchronized的作用和用法

    郁闷 参考 synchronized的作用和用法 Java中Synchronized的使用 文章目录 简单介绍 用法 实战实例 修饰代码块 修饰普通方法 修饰静态方法 简单介绍 synchronized关键字是用来控制线程同步的 就是在多线
  • 锁介绍名词解释&&Lock && synchronized

    各种锁名词解释及应用 一 名词解释 1 乐观锁 VS 悲观锁 2 自旋锁 VS 适应性自旋锁 3 无锁 VS 偏向锁 VS 轻量级锁 VS 重量级锁 4 公平锁 VS 非公平锁 5 可重入锁 VS 非可重入锁 6 独享锁 VS 共享锁 二
  • 接口并发性能测试开发之:从测试方案设计、测试策略、指标分析到代码编写,这一篇全搞定。

    并发接口性能设计思路与代码编写 1 引言 2 并发测试定义 3 并发测试分类 4 设计思路整理 5 测试方案设计 6 指标分析 7 代码实战 8 总结 1 引言 这篇是我3月份在公司内部做的技术分享内容 由于我在公司内部分享的内容较多 以及
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被

随机推荐

  • IIS上部署Django+vue-element-admin-master

    在Windows2012上通过IIS部署自己的web Django Vue element admin master 文章目录 前言 1 安装IIS和CGI 2 部署Django项目 3 部署vue element admin master
  • 二极管 MOS管 3.23学习笔记

    二级管 外加正向电压导通 外加反向电压截至 受电压极性控制的开关 缺点 1 由于二极管的导通压降 造成输出的电压与输入的高低电压有偏移 向下一级门电 路传递时 有高低电压的偏差 2 带负载能力差 MOS管 栅极 源极 漏极 衬底 当门极与衬
  • Mybatis-Plus insertBatch执行缓慢原因查询

    背景 最近在SpringCloud项目中 使用Mybatis Plus执行一个88万条左右的数据插入MySQL数据库的操作时 发现执行时长竟然长达2个小时 按理讲 MP框架执行如下批处理操作时 XXService insertBatch X
  • 【PyCharm警告】选择性忽略 PEP8 警告

    提示 Class names should use CamelCase convention Inspection info This inspection checks the PEP8naming conventions 为什么 从命名
  • spark SQL基础教程

    1 sparkSQL入门 sparksql专门用于处理结构化的数据 而RDD还可以处理非结构化的数据 sparksql的优点之一是sparkfsql使用统一的api读取不同的数据 第二个优点是可以在语言中使用其他语言 例如python 另外
  • 21电赛D题配置部分

    MJPG Streamer推流 安装MJPG Streamer 编辑 etc apt sources list 文件 删除原文件所有内容 用以下内容取代 deb http mirrors tuna tsinghua edu cn raspb
  • jest搭建vue项目单元测试-现有老项目

    说到项目会分为新建的醒目和老项目两种 jest搭建vue项目单元测试 vue cli创建新项目 我们接下来说现有老项目 现有的vue老项目或者没使用vue cli创建项目搭建jset单元测试 1 安装 npm i vue test util
  • 重启Vcenter命令

    重启Vcenter命令 通过ssh登录Vcenter 输入root 输入shell 输入service control stop all 输入service control start all 停止 启动或重新启动 VMware vCent
  • linux xenserver教程,XenServer常用命令

    监控检查类 xentop 查看XenServer与VM的资源使用情况 xsconsole 进入XenServer管理面板 查看网卡 IP 系统版本 系统时间 硬件信息等 xe task list 查看XenServer临时任务进程 serv
  • 【C语言】验证哥德巴赫猜想

    文章目录 问题来源 题目要求 如何判断素数 主函数 完整代码 效果演示 写代码中的误解 总结 问题来源 这是学校的一个作业 原题如下 题目先给出了哥德巴赫猜想的背景知识 我还真不知道 2000以内的正偶数都能分解成两个质数 素数 之和 题目
  • 毕业设计-基于 PID 控制算法仿真算法研究- Matlab

    目录 前言 课题背景和意义 实现技术思路 一 基本原理 二 无超调 PID 控制器的设计 三 无超调 PID 设计的验证 代码 实现效果图样例 最后 前言 大四是整个大学期间最忙碌的时光 一边要忙着备考或实习为毕业后面临的就业升学做准备 一
  • 安装anaconda及修改conda config 的channels/default_channels

    先说一下安装anaconda的方法 很简单 就是去官网下载然后在本地安装 bash Anaconda3 4 4 0 Linux x86 64 sh 这个过程中要耐心 会有提问 需要输入yes来回应 并且需要按很多的回车 总之 看见让输入ye
  • 转:机器学习的理解

    转李航博士的一篇关于机器学习理解的文章 算算时间 从开始到现在 做机器学习算法也将近八个月了 虽然还没有达到融会贯通的地步 但至少在熟悉了算法的流程后 我在算法的选择和创造能力上有了不小的提升 实话说 机器学习很难 非常难 要做到完全了解算
  • Ridis持久化

    Redis持久化 RDB Redis DataBase Redis会单独创建 fork 一个子进程来进行持久化 会先将数据写入到一个临时文件中 待持久化都结束了 再用这个临时文件替换上次持久化好的文件 整个过程中 主进程是不进行io操作的
  • 8--UI 初步认识 简易计算器

    UI是App的根基 一个App应该是先有UI界面 然后在UI的基础上增加实用功能 2 UI相对简单易学 UI普遍是学习过程中最简单的一块 能快速拥有成就感和学习兴趣 3 UI至关重要 开发中的绝大部分时间都在处理UI 谨记一条IOS软件开发
  • MySQL根据某一个或者多个字段查找重复数据

    sql 查出一张表中重复的所有记录数据 1 表中有id和name 两个字段 查询出name重复的所有数据 select from xi a where a username in select username from xi group
  • 系列教程

    PDF Search 系列教程来咯 在 Part 1 中 我们将演示如何从 PDF 中提取 处理并存储图像及文本 随着神经搜索 Neural Search 技术的普及 越来越多开发者 开始尝试用 Jina 解决非结构化数据的索引和搜索问题
  • MySQL必知必会 学习笔记 第二十五章 使用触发器

    触发器在MySQL 5中增加 触发器可以在MySQL响应DELETE INSERT UPDATE语句时自动执行一条SQL语句 MySQL 5中触发器名在每个表中唯一而不是在一个数据库中唯一 其他DBMS有的重名限制是数据库范围 以后MySQ
  • lua和测试(一)

    lua做为一门高级语言 在游戏产业运用到机会越来越多了 测试掌握几门脚本语言也有一定的重要性 以下对于lua组合输入做出一些引导 测试需要掌握的关于返回数值 主要用到布尔类 前言的指引 lua的语法比较简单和清晰 学过c语言的可以很好的掌握
  • 并发编程系列之自定义线程池

    前言 前面我们在讲并发工具类的时候 多次提到线程池 今天我们就来走进线程池的旅地 首先我们先不讲线程池框架Executors 我们今天先来介绍如何自己定义一个线程池 是不是已经迫不及待了 那么就让我们开启今天的旅途吧 什么是线程池 线程池可