Java自带的线程池及调用、ThreadPoolExecutor类(线程池的7大参数)、任务队列及底层原理

本文主要是介绍Java自带的线程池及调用、ThreadPoolExecutor类(线程池的7大参数)、任务队列及底层原理,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

day32

线程池

引入

一个线程完成一项任务所需时间为:

  1. 创建线程时间 - Time1
  2. 线程中执行任务的时间 - Time2
  3. 销毁线程时间 - Time3

注意:优化在Time1,Time3(创建销毁线程费时间)

为什么需要线程池

  1. 线程池技术正是关注如何缩短或调整Time1和Time3的时间,从而提高程序的性能。项目中可以把Time1,T3分别安排在项目的启动和结束的时间段或者一些空闲的时间段
  2. 线程池不仅调整Time1,Time3产生的时间段,而且它还显著减少了创建线程的数目,提高线程的复用率
  3. 系统启动一个新线程的成本是比较高的,因为涉及与操作系统的交互,在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存期很短暂的线程时,优先考虑使用线程池

为什么使用线程池?

1.优化并调成创建线程和销毁线程的事件

2.提高线程的复用率

3.有效控制项目中的线程目数

Java提供的线程池

ExecutorService:线程池的接口

Executors:创建各种线程池的工具类

理解:实现了ExcutorService接口;底层:死循环,监视有没有提交任务,所以pool.shutdown关闭

分类

1.单个线程的线程池

2.指定线程的线程池

3.可缓存线程的线程池

理解:先没有线程,等有任务就添加一定数量的线程,去分批次执行,等有线程空闲了就干掉

缺点:线程目数不可控

4.延迟任务的线程池

多少时间运行一次(TimeUnit提供了很多时间单位)

public class Test {public static void main(String[] args) {//创建单个线程的线程池//ExecutorService pool = Executors.newSingleThreadExecutor();//创建指定线程的线程池//ExecutorService pool = Executors.newFixedThreadPool(3);//创建可缓存线程的线程池,自动回收60s闲置线程ExecutorService pool = Executors.newCachedThreadPool();//循环创建任务对象,并提交给线程池for (int i = 1; i <= 100; i++) {pool.execute(new Task(i));//提交任务,i为任务编号(方便理解)}pool.shutdown();//关闭线程池}
}
class Task implements Runnable{private int i;public Task(int i) {this.i = i;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "-->" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}
}
public class Test01 {public static void main(String[] args) throws InterruptedException {//获取延迟任务的线程池ScheduledExecutorService pool = Executors.newSingleThreadScheduledExecutor();//设置延迟时间(时间,时间单位)pool.awaitTermination(6, TimeUnit.SECONDS);//循环创建任务对象,并提交给线程池for (int i = 1; i <= 100; i++) {//创建任务对象Task task = new Task(i);//提交任务pool.execute(task);}//关闭线程池pool.shutdown();	}
}
public class Task implements Runnable{private int num;public Task(int num) {this.num = num;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "处理了第" + num + "个任务");}}

深入源码

ExecutorService pool = Executors.newSingleThreadExecutor();

ExecutorService pool = Executors.newFixedThreadPool(3);

ExecutorService pool = Executors.newCachedThreadPool();

三种线程池底层都是ThreadPoolExecutor类的对象

-- 分析ThreadPoolExecutor类的构造方法源码--------------------------------
public ThreadPoolExecutor(int corePoolSize,		------------- 核心线程数量 int maximumPoolSize,	------------- 最大线程数量 long keepAliveTime,		------------- 闲置时间,作用于核心线程数与最大线程数之间的线程TimeUnit unit,			------------- keepAliveTime的时间单位(可以是毫秒、秒....)BlockingQueue<Runnable> workQueue, -- 任务队列ThreadFactory threadFactory, -------- 线程工厂RejectedExecutionHandler handler ---- 达到了线程界限和队列容量时的处理方案(拒绝策略)
) {}
执行步骤:1.创建线程池后2.任务提交后,查看是否有核心线程:3.1 没有 -> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中3.2-> 查看是否有闲置核心线程:4.1-> 执行任务 -> 执行完毕后又回到线程池4.2 没有 -> 查看当前核心线程数是否核心线程数量:5.1-> 就创建核心线程 -> 执行任务 -> 执行完毕后又回到线程池中5.2-> 查看任务列表是否装载满:6.1 没有 -> 就放入列表中,等待出现闲置线程6.2 装满 -> 查看是否有普通线程(核心线程数到最大线程数量之间的线程)7.1 没有 -> 就创建普通线程 -> 执行任务 -> 执行完毕后又回到线程池中7.2-> 查看是否有闲置普通线程7.1.1-> 执行任务 -> 执行完毕后又回到线程池中7.1.2 没有 -> 查看现在所有线程数量是否为最大线程数:8.1-> 执行处理方案(默认处理抛出异常)8.2->就创建普通线程-> 执行任务 -> 执行完毕后又回到线程池中     			
注:1.为了更好的理解,在这里区分核心线程和普通线程,实际上区分的这么清楚,都是线程2.默认的处理方案就是抛出RejectedExecutionException
总结:核心线程满载 -> 任务队列 -> 普通线程 -> 拒绝策略-- 分析单个线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newSingleThreadExecutor();
new ThreadPoolExecutor(1, -- 核心线程数量 1, -- 最大线程数量 0L, -- 闲置时间TimeUnit.MILLISECONDS, -- 时间单位(毫秒)new LinkedBlockingQueue<Runnable>() -- 无界任务队列,可以无限添加任务
)  
-- 分析指定线程的线程池的源码 --------------------------------
ExecutorService pool = Executors.newFixedThreadPool(3);
new ThreadPoolExecutor(nThreads, -- 核心线程数量 nThreads, -- 最大线程数量 0L, -- 闲置时间TimeUnit.MILLISECONDS, -- 时间单位(毫秒)new LinkedBlockingQueue<Runnable>()-- 无界任务队列,可以无限添加任务
)
-- 创建可缓存线程的线程池 -----------------------------------
new ThreadPoolExecutor(0, -- 核心线程数量 Integer.MAX_VALUE,-- 最大线程数量 60L, -- 闲置时间TimeUnit.SECONDS, -- 时间单位()new SynchronousQueue<Runnable>() -- 直接提交队列(同步队列):没有容量队列

任务队列详解

队列名称详解
LinkedBlockingQueue无界任务队列使用无界任务队列,线程池的任务队列可以无限制的添加新的任务,而线程池创建的最大线程数量就是你corePoolSize设置的数量,也就是说在这种情况下maximumPoolSize这个参数是无效的,哪怕你的任务队列中缓存了很多未执行的任务,当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待,当使用这种任务队列模式时,一定要注意你任务提交与处理之间的协调与控制,不然会出现队列中的任务由于无法及时处理导致一直增长,直到最后资源耗尽的问题
SynchronousQueue 同步任务队列 直接提交任务队列使用直接提交任务队列,队列没有容量,每执行一个插入操作就会阻塞,需要再执行一个删除操作才会被唤醒,反之每一个删除操作也都要等待对应的插入操作。 任务队列为SynchronousQueue,创建的线程数大于maximumPoolSize时,直接执行了拒绝策略抛出异常。 使用SynchronousQueue队列,提交的任务不会被保存,总是会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize,则尝试创建新的线程,如果达到maximumPoolSize设置的最大值,则根据你设置的handler执行拒绝策略。因此这种方式你提交的任务不会被缓存起来,而是会被马上执行,在这种情况下,你需要对你程序的并发量有个准确的评估,才能设置合适的maximumPoolSize数量,否则很容易就会执行拒绝策略;
ArrayBlockingQueue有界任务队列使用有界任务队列,若有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中。若等待队列已满,即超过ArrayBlockingQueue初始化的容量,则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。在这种情况下,线程数量的上限与有界任务队列的状态有直接关系,如果有界队列初始容量较大或者没有达到超负荷的状态,线程数将一直维持在corePoolSize以下,反之当任务队列已满时,则会以maximumPoolSize为最大线程数上限。
PriorityBlockingQueue优先任务队列使用优先任务队列,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量,只不过其他队列一般是按照先进先出的规则处理任务,而PriorityBlockingQueue队列可以自定义规则根据任务的优先级顺序先后执行。

对优先队列的使用说明:

public class Test {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 1000, TimeUnit.MILLISECONDS, new PriorityBlockingQueue<>(), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());for (int i = 1; i <= 10; i++) {pool.execute(new Task(i));}pool.shutdown();}
}
class Task implements Runnable,Comparable<Task>{private int priority;public Task(int priority) {this.priority = priority;}@Overridepublic void run() {try {Thread.sleep(1000);System.out.println(Thread.currentThread().getName() + " priority:" + this.priority);} catch (InterruptedException e) {e.printStackTrace();}}//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高@Overridepublic int compareTo(Task o) {return (this.priority>o.priority)?-1:1;}
}
总结:除了第一个任务直接创建线程执行外,其他的任务都被放入了优先任务队列,按优先级进行了重新排列执行,且线程池的线程数一直为corePoolSize,也就是只有一个。
代码实现
无界队列

理解:这个队列没有上限

继承关系:LinkedBlockingQueue -> AbstractQueue -> AbstractCollection

小结:

1.LinkedBlockingQueue是Collection集合家族的一员

2.Collection集合家族(List、Set、Queue)

3.LinkedBlockingQueue数据结构单向链表

缺点:LinkedBlockingQueue可能造成内存溢出

有界队列

继承关系:ArrayBlockingQueue -> AbstractQueue -> AbstractCollection

小结:

1.ArrayBlockingQueue是Collection集合家族的一员

2.Collection集合家族(List、Set、Queue)

3.ArrayBlockingQueue数据结构一维数组

public class Test01 {public static void main(String[] args) throws InterruptedException {//创建无界队列LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();//创建有界队列//ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(6);//添加元素queue.put("aaa");queue.put("bbb");queue.put("ccc");queue.put("ddd");queue.put("eee");queue.put("fff");//删除元素queue.remove("ccc");//遍历队列Iterator<String> it = queue.iterator();while (it.hasNext()) {String string = it.next();System.out.println(string);}	}
}
优先队列

继承关系:ArrayBlockingQueue -> AbstractQueue -> AbstractCollection

小结:

1.ArrayBlockingQueue是Collection集合家族的一员

2.Collection集合家族(List、Set、Queue)

3.ArrayBlockingQueue数据结构一维数组

public class Test03 {public static void main(String[] args) throws InterruptedException {//创建优先队列PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();//无参构造底层使用的是元素的内置比较器//添加元素queue.add(new Task(3));queue.add(new Task(1));queue.add(new Task(4));queue.add(new Task(2));//遍历取出队列while(!queue.isEmpty()){//删除第一元素Task poll = queue.poll();System.out.println(poll.getPriority());}}
}

Task类

public class Task implements Runnable,Comparable<Task>{private int priority;//优先级别public Task(int priority) {this.priority = priority;}@Overridepublic void run() {System.out.println("任务被处理了");}//当前对象和其他对象做比较,当前优先级大就返回-1,优先级小就返回1,值越小优先级越高@Overridepublic int compareTo(Task o) {return Integer.compare(this.priority, o.priority);}public int getPriority() {return priority;}
}

总结

1.Java自带的线程池
单个线程的线程池
指定线程个数的线程池
可缓存的线程池
延迟任务的线程池

2.线程池的7大参数
核心线程数
最大线程数
任务队列(有界、无界、同步、优先队列)
拒绝策略
闲置时间
时间单位
线程工厂

3.线程池的调用步骤(核心线程、任务队列、普通线程、拒绝策略)

4.任务队列及底层原理(有界、无界、同步、优先队列)

这篇关于Java自带的线程池及调用、ThreadPoolExecutor类(线程池的7大参数)、任务队列及底层原理的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/861294

相关文章

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

Java注解之超越Javadoc的元数据利器详解

《Java注解之超越Javadoc的元数据利器详解》本文将深入探讨Java注解的定义、类型、内置注解、自定义注解、保留策略、实际应用场景及最佳实践,无论是初学者还是资深开发者,都能通过本文了解如何利用... 目录什么是注解?注解的类型内置注编程解自定义注解注解的保留策略实际用例最佳实践总结在 Java 编程

Java 实用工具类Spring 的 AnnotationUtils详解

《Java实用工具类Spring的AnnotationUtils详解》Spring框架提供了一个强大的注解工具类org.springframework.core.annotation.Annot... 目录前言一、AnnotationUtils 的常用方法二、常见应用场景三、与 JDK 原生注解 API 的

Java controller接口出入参时间序列化转换操作方法(两种)

《Javacontroller接口出入参时间序列化转换操作方法(两种)》:本文主要介绍Javacontroller接口出入参时间序列化转换操作方法,本文给大家列举两种简单方法,感兴趣的朋友一起看... 目录方式一、使用注解方式二、统一配置场景:在controller编写的接口,在前后端交互过程中一般都会涉及

Java中的StringBuilder之如何高效构建字符串

《Java中的StringBuilder之如何高效构建字符串》本文将深入浅出地介绍StringBuilder的使用方法、性能优势以及相关字符串处理技术,结合代码示例帮助读者更好地理解和应用,希望对大家... 目录关键点什么是 StringBuilder?为什么需要 StringBuilder?如何使用 St

使用Java将各种数据写入Excel表格的操作示例

《使用Java将各种数据写入Excel表格的操作示例》在数据处理与管理领域,Excel凭借其强大的功能和广泛的应用,成为了数据存储与展示的重要工具,在Java开发过程中,常常需要将不同类型的数据,本文... 目录前言安装免费Java库1. 写入文本、或数值到 Excel单元格2. 写入数组到 Excel表格

redis中使用lua脚本的原理与基本使用详解

《redis中使用lua脚本的原理与基本使用详解》在Redis中使用Lua脚本可以实现原子性操作、减少网络开销以及提高执行效率,下面小编就来和大家详细介绍一下在redis中使用lua脚本的原理... 目录Redis 执行 Lua 脚本的原理基本使用方法使用EVAL命令执行 Lua 脚本使用EVALSHA命令

Java并发编程之如何优雅关闭钩子Shutdown Hook

《Java并发编程之如何优雅关闭钩子ShutdownHook》这篇文章主要为大家详细介绍了Java如何实现优雅关闭钩子ShutdownHook,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起... 目录关闭钩子简介关闭钩子应用场景数据库连接实战演示使用关闭钩子的注意事项开源框架中的关闭钩子机制1.