JAVA-快速了解线程池的基本原理
一、并发队列
1. 并发队列概念
并发队列是一个基于链接节点的无界线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部,当我们获取一个元素时,它会返回队列头部的元素。
2. 并发队列分类
并发队列分为阻塞队列和非阻塞队列,下面举例示意:
现有一个长度为10的队列,有11个元素需要放进去
两种队列区别
入队时
非阻塞队列:当向队列中放入10个元素,此时队列已满,再放入第11个元素数据就会丢失。
阻塞队列:当队列已满了的时候,此时会进行等待,什么时候队列中有出队的元素,那么第11个再放进去。
出队时
非阻塞队列:如果队列中没有元素了,此时进行出队操作,往外取元素,得到的就是null。
阻塞队列:当队列中没有元素时,如果此时进行出队操作会等待,什么时候放进去,什么时候再取出来。
特别地,线程池就是基于阻塞队列实现的。
二、线程池简介
线程池是一种多线程处理形式,处理过程中将任务添加到队列,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务。执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
简单来说,线程池就是线程的集合。
三、为什么需要线程池
线程的正常生命周期如下图所示:
为了便于分析,假设各阶段所花时间如上所示(当然线程各阶段实际所花时间极短,为毫秒级)。如果我们能省略其他阶段,每次线程直接运行任务,这样就可以单个线程处理任务就可以节省5秒。要实现这样的设想,我们可以使用线程池来处理,因为线程池中的线程是事先创建好的大量空闲线程,当队列中的任务进入线程池中,线程可以直接执行任务,执行完成后释放资源,继续处理下一任务。
举例来看:现有100个任务需要处理,一次最多创建10个线程。如果采用普通方式,一次创建10个线程处理10个任务,总共需60秒,而采用线程池的方式,一次执行10个任务,总共需要10秒。
综上所述:我们可以很明显的看出线程池在处理任务量极大的高并发系统中,具有很大的优势。
四、线程池的原理
1. ThreadPoolExecutor核心类
线程池的最上层接口是Executor,这个接口定义了一个核心方法execute(Runnablecommand),这个方法是用来传入任务的,最后被ThreadPoolExecutor类实现。而且ThreadPoolExecutor是线程池的核心类,此类的构造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnitunit,BlockingQueue<Runnable>workQueue,RejectedExecutionHandler handler); public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
各个参数表示意义:
参数名 参数含义
corePoolSize 核心线程池大小,也即核心线程的数量
maximumPoolSize 最大线程池大小,也即线程的最大数量
keepAliveTime 空闲时间,是除核心线程之外的新创建线程的最大存活时间
TimeUnit 时间单位
workQueue 阻塞队列,用来存储等待的任务
threadFactory 线程工厂,用来创建新线程
handler 拒绝处理策略,当提交给线程池的任务量超过最大线程池大小+队列长度,就会采取拒绝处理策略
特别地说明:
workQueue一般有以下三种阻塞队列:
SynchronousQueue:直接提交,默认使用队列
ArrayBlockingQueue:有界队列
LinkedBlockingQueue:无界队列
threadFactory是当队列已满,但线程总数量<最大线程池大小时,线程池中用来创建新线程的线程工厂。一般有下列三种类型:
ArrayBlockingQueue:有界线程安全的阻塞队列。
LinkedBlockingQueue:并发安全的阻塞队列。
SynchronousQueue:同步队列。
handler触发时,有以下四种拒绝处理策略:
hreadPoolExecutor.AbortPolicy(默认):丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
2.线程池原理图
3. 线程池实例
接下来就通过一个简单的实例并结合原理图来了解线程池的基本原理:
public class test02 { public static void main(String[] args) { ThreadPoolExecutor pool = new ThreadPoolExecutor(1,2,3, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3)); //利用线程池中的线程开始执行任务 //执行第一个任务 pool.execute(new TestThread()); //队列有三个任务等待 pool.execute(new TestThread()); pool.execute(new TestThread()); pool.execute(new TestThread()); //执行第五个任务 pool.execute(new TestThread()); //执行第六个任务,拒绝任务报错 //pool.execute(new TestThread()); //当前线程池中有2个线程:1个核心线程 + 1个新创建的线程 = 最大线程数 //关闭线程池 pool.shutdown(); } } class TestThread implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()); } }
首先创建一个最简单类型的线程池,构造方法只有五个参数,每个参数意义如下:
1:核心线程数
2:最大线程数
3:空闲时间。新创建的线程执行任务后等待新任务的空闲时间
TimeUnit.SECONDS:时间单位,秒
new LinkedBlockingDeque:阻塞队列,长度为3
不执行第6条任务时的执行结果如下:
执行第6条任务时执行结果如下:
分析代码执行过程:
现有一线程池,里面只有一个核心线程thread1,第一个任务进入线程池中,由thread1执行,而2-4号线程处在队列中等待执行,当5号任务提交时,根据原理图,此时满足队列已满,且核+新<=最大,所以创建新线程thread2,由thread1和thread2分摊执行任务,由运行结果也可以看出,确实是分摊任务。
当加上第6条的任务时,根据原理图,此时队列已满,且核+新>最大,没有多余的线程执行任务,队列也无法装入,就会报错,拒绝任务。
五、线程池的分类
线程池可分为以下四类:
1. 可缓存:newCachedThreadPool
作用:创建一个根据需要创建新线程池的线程池。当旧线程释放资源后就可以使用旧线程。
特点:线程数灵活最大值为INTER.MAX_VALUE,底层采用一个近似无边界队列
2. 定长:newFixedThreadPool
作用:创建一个可重用固定线程数的线程池,以共享的无界队列来运行这些线程。
特点:线程处于一定量,可以很好的控制并发量
3. 定时:newScheduleThreadPool
作用:创建一个可延迟或延期运行的线程池。
特点:线程池中具有指定数量的线程,可定时或延迟执行,适用于周期性执行任务的场景。
4. 单例:newSingleThreadExecutor
作用:创建一个只有一个线程的线程池。且线程的存货时间是无限的,当该线程正繁忙时,对于新任务会进入无界的阻塞队列中。
特点:适用于一个一个任务执行的场景。
相关文章
基于springcloud异步线程池、高并发请求feign的解决方案
这篇文章主要介绍了基于springcloud异步线程池、高并发请求feign的解决方案,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25- 这篇文章主要介绍了Java线程池中的各个参数如何合理设置操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教...2021-06-19
- 在Java中,我们可以利用多线程来最大化地压榨CPU多核计算的能力,下面这篇文章主要给大家介绍了关于java中多线程与线程池基本使用的相关资料,需要的朋友可以参考下...2021-09-13
- 这篇文章主要介绍了教你如何监控 Java 线程池运行状态的操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-27
- 这篇文章主要介绍了C#多线程ThreadPool线程池的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25
- 在C#编程语言中,使用线程池可以并行地处理工作,当强制线程和更新进度条时,会使用内建架构的ThreadPool类,为批处理使用多核结构,这里我们来看在C#编程语言中一些关于来自System.Threading的ThreadPool的用法的例子...2020-06-25
java并发包中CountDownLatch和线程池的使用详解
这篇文章主要介绍了java并发包中CountDownLatch和线程池的使用详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-18- 今天我们来学习一下Java ThreadPoolExecutor的使用,文中有非常详细的介绍及代码示例,对正在学习Java的小伙伴们有很大的帮助,需要的朋友可以参考下...2021-05-15
基于C++11的threadpool线程池(简洁且可以带任意多的参数)
C++11 加入了线程库,从此告别了标准库不支持并发的历史。然而 c++ 对于多线程的支持还是比较低级,稍微高级一点的用法都需要自己去实现,譬如线程池、信号量等...2020-04-25- 这篇文章主要介绍了Java 自定义线程池和线程总数控制操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-27
- 本文介绍的线程池采用C++语言,在windows平台下实现。本着技术分享的精神写作本文同时公布源代码。欢迎大家指出该线程池存在的问题并对当前性能进行讨论。...2020-04-25
- 本篇文章是对利用ace的ACE_Task等类实现线程池的方法进行了详细的分析介绍,需要的朋友参考下...2020-04-25
- 这篇文章主要介绍了C++线程池的简单实现方法,包括了线程操作函数及相关属性的用法,需要的朋友可以参考下...2020-04-25
- 这篇文章主要介绍了C#线程处理系列之线程池中的I/O线程,在这篇文章中将介绍如何用线程池中的I/O线程来执行I/O操作,感兴趣的小伙伴们可以参考一下...2020-06-25
- 这篇文章主要介绍了java 线程池keepAliveTime的含义说明,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-18
- 现在由于系统越来越复杂,导致很多接口速度变慢,这时候就会想到可以利用线程池来处理一些耗时并不影响系统的操作。本文就介绍了SpringBoot线程池的使用,感兴趣的可以了解一下...2021-06-23
- 这里给大家介绍了C++中对于pthread线程的一个简单应用以及使用继承CDoit,实现其中的start和end,有需要的小伙伴可以参考下...2020-04-25
- 这篇文章主要介绍了C++11 简单实现线程池的方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-10-27
- 这篇文章主要介绍了C#多线程学习之使用线程池进行多线程的自动管理,实例分析了C#中线程池的概念与相关的使用技巧,非常具有实用价值,需要的朋友可以参考下...2020-06-25
- 线程池,简单来说就是有一堆已经创建好的线程,接下来通过本文给大家介绍C/C++ 原生API实现线程池的方法,感兴趣的朋友跟随小编一起看看吧...2021-11-02