【并发编程】线程池

news/2024/12/29 13:53:11/

背景

线程的创建和销毁都需要很大的开销,当线程数量过大,并且线程生命周期短。这时候线程频繁地创建和销毁就很没有必要。

在 Java 中可以通过线程池来解决此问题。线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用

在 JDK5 之前,我们必须手动实现自己的线程池,从 JDK5 开始,Java 内置支持线程池。在 JDK5 版本中增加了内置线程池实现 ThreadPoolExecutor,同时提供了Executors 来创建不同类型的线程池。Executors 中提供了以下常见的线程池创 建方法:

newSingleThreadExecutor:一个单线程的线程池。如果因异常结束,会再创建一个新的,保证按照提交顺序执行。

newFixedThreadPool:创建固定大小的线程池。根据提交的任务逐个增加线程,直到最大值保持不变。如果因异常结束,会新创建一个线程补充。

newCachedThreadPool:创建一个可缓存的线程池。会根据任务自动新增或回收线程。

虽然在 JDK 中提供 Executors 类来支持以上类型的线程池创建,但通常情况下不建议开发人员直接使用(见《阿里巴巴 java 开发规范》并发处理)。

线程池优点:

  • 重复利用线程,降低线程创建和销毁带来的资源消耗 。

  • 统一管理线程,线程的创建和销毁都由线程池进行管理 。

  • 提高响应速度,线程创建已经完成,任务来到可直接处理,省去了创建时间。

ThreadPoolExecutor 类

ThreadPoolExecutor 继承了 AbstractExecutorService 类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

核心源码

public class ThreadPoolExecutor extends AbstractExecutorService {public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,threadFactory, defaultHandler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);}public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
}

构造器中各个参数的含义

corePoolSize:核心池的大小。在创建了线程池后,默认情况下,在创建了线程池后,线程池中的线程数为 0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到 corePoolSize 后,就会把到达的任务放到缓存队列当中;除非调用了prestartAllCoreThreads()或者 prestartCoreThread()方法,在没有任务到来之前就创建corePoolSize 个线程或者一个线程。

maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于 corePoolSize 时,keepAliveTime 才会起作用,直到线程池中的线程数不大于 corePoolSize,即当线程池中的线程数大于 corePoolSize 时,如果一个线程空闲的时间达到 keepAliveTime,则会终止,直到线程池中的线程数不超过 corePoolSize。

unit:参数 keepAliveTime 的时间单位,有 7 种取值,在 TimeUnit 类中有 7种静态属性:

TimeUnit.NANOSECONDS;          //纳秒
​
TimeUnit.MICROSECONDS;         //微秒
​
TimeUnit.MILLISECONDS;         //毫秒
​
TimeUnit.SECONDS;              //秒
​
TimeUnit.MINUTES;              //分钟
​
TimeUnit.HOURS;                //小时
​
TimeUnit.DAYS;                 //天

workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很

重要,会对线程池的运行过程产生重大影响

threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略 ;

线程池的执行

创建完成 ThreadPoolExecutor 之后,当向线程池提交任务时,通常使用 execute 方法。execute 方法的执行流程图如下:

  1. 如果线程池中存活的核心线程数小于线程数 corePoolSize 时,线程池会创建一个核心线程去处理提交的任务。

  2. 如果线程池核心线程数已满,即线程数已经等于 corePoolSize,一个新提交的任务,会被放进任务队列workQueue 排队等待执行。3. 当线程池里面存活的线程数已经等于 corePoolSize 了,并且任务队列workQueue也满,判断线程数是否达到 maximumPoolSize,即最大线程 数是否已满,如果没到达,创建一个非核心线程执行提交的任务。

  3. 如果当前的线程数达到了 maximumPoolSize,还有新的任务过来的话,直接采用拒绝策略处理。

线程池中的队列

线程池有以下工作队列:

ArrayBlockingQueue:是一个用数组实现的有界阻塞队列,创建时必须设置长度,,按 FIFO 排序量。

LinkedBlockingQueue:基于链表结构的阻塞队列,按 FIFO 排序任务,容量可以选择进行设置,不设置是一个最大长度为 Integer.MAX_VALUE;

线程池的拒绝策略

构造方法的中最后的参数 RejectedExecutionHandler 用于指定线程池的拒绝策略。当请求任务不断的过来,而系统此时又处理不过来的时候,我们就需要采取对应的策略是拒绝服务。

默认有四种类型:

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

CallerRunsPolicy 策略:只要线程池未关闭,该策略在调用者线程中运行当前的任务(如果任务被拒绝了,则由提交任务的线程(例如:main)直接执行此任务)。

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

DiscardPolicy 策略:该策略丢弃无法处理的任务,不予任何处理。

execute 与 submit 的区别

执行任务除了可以使用 execute 方法还可以使用 submit 方法。

它们的主要区别是:execute 适用于不需要关注返回值的场景,submit 方法适用于需要关注返回值的场景。

关闭线程池

关闭线程池可以调用 shutdownNow 和 shutdown 两个方法来实现。

shutdownNow:对正在执行的任务全部发出 interrupt(),停止执行,对还未开始执行的任务全部取消,并且返回还没开始的任务列表。

shutdown:当我们调用 shutdown 后,线程池将不再接受新的任务,但也不会去强制终止已经提交或者正在执行中的任务


http://www.ppmy.cn/news/984147.html

相关文章

SwipeDelMenuLayout失效:Could not find SwipeDelMenuLayout-V1.3.0.jar

一、问题描述 最近在工作上的项目中接触到SwipeDelMenuLayout这个第三方Android开发库&#xff0c;然后我就根据网上的教程进行配置。这里先说一下我的开发环境&#xff1a;Android Studio版本是android-studio-2020.3.1.24-windows&#xff0c;gradle版本是7.0.2。 首先是在se…

IT行业面试攻略:技巧与心态的平衡

引言&#xff1a;在面试IT公司时&#xff0c;调整好心态是取得优秀表现的关键。面试心态直接影响着我们在面试中的自信程度和表现。面对这一挑战&#xff0c;我们需要学会积极自信、认识到紧张是正常的、进行充分准备以及以积极的心态去迎接面试。只有在拥有正确的心态下&#…

一文谈谈Git

"And if forever lasts till now Alright" 为什么要有git&#xff1f; 想象一下&#xff0c;现如今你的老师同时叫你和张三&#xff0c;各自写一份下半年的学习计划交给他。 可是你的老师是一个极其"较真"的人&#xff0c;发现你俩写的学习计划太"水&…

VScode 避免逗号、括号时自动补全

设置项 控制是否应在遇到提交字符时接受建议。例如&#xff0c;在JavaScript中&#xff0c;半角分号(;)可以为提交字符&#xff0c;能够在接受建议的同时键入该字符。 "editor.acceptSuggestionOnCommitCharacter": false起因 比如打伪代码的时候输入一些缺少上下…

探索单例模式:设计模式中的瑰宝

文章目录 常用的设计模式有以下几种&#xff1a;一.创建型模式&#xff08;Creational Patterns&#xff09;&#xff1a;二.结构型模式&#xff08;Structural Patterns&#xff09;&#xff1a;三.行为型模式&#xff08;Behavioral Patterns&#xff09;&#xff1a;四.并发…

【Linux从入门到精通】进程的控制(进程退出+进程等待)

本篇文章主要讲述的是进程的退出和进程等待。希望本篇文章的内容会对你有所帮助。 文章目录 一、fork创建子进程 1、1 在创建子进程中操作系统的作用 1、2 写时拷贝 二、进程终止 2、1 常见的进程退出 2、2 进程的退出码 2、2、1 运行结果正确实例 2、2、2 运行结果不正确实例…

Python补充笔记5-模块化、文件

目录 一、模块 二、模块的导入 三、python中的包​编辑 四、常用的内容模块 五、第三方模块的安装与使用 六、编码格式的介绍 七、文件读写的原理 八、常用的文件打开模式 ​九、文件对象的常用方法 十、with语句​编辑 十一、os模块的常用函数 十二、os.path模块的常用方法​编…

GIT涵盖工作中用的相关指令

git安装一直默认点击下去&#xff0c;安装完成&#xff0c;右键会看见gitBash git --version 查看git安装的版本 使用git前配置git git config --global user.name 提交人姓名 git config --global user.email 提交人邮箱 git config --list 查看git配置信息 使用git中配置…