您当前的位置:首页 > 电脑百科 > 程序开发 > 架构

什么是线程池?线程池ThreadPoolExecutor使用及其原理又是什么?

时间:2020-08-03 11:25:45  来源:  作者:

线程池,顾名思义,用来存放线程的一个容器

先了解一下线程的生命周期

什么是线程池?线程池ThreadPoolExecutor使用及其原理又是什么?

 

我们为什么要用线程池?

技术的发展无非就是需求推动的,而技术领域的需求大部分都是快!再快!更快!

那么线程池出现的需求也就是痛点是什么呢?

第一、线程的创建和销毁是要占用一定的资源的,创建线程会直接向系统申请,调用系统函数进行分配资源。操作系统给线程分配内存、列入调度,同时线程还要进行上下文的切换。

第二、在JAVA中,线程的线程栈所占用的内存在Java堆外,不受Java程序控制,只受系统资源限制,默认一个线程的线程栈大小是1M(当然这个可以通过设置-Xss属性设置,但是要注意栈溢出问题)。如果每个请求都新建线程,1024个线程就会占用1个G内存,系统很容易崩溃。

第三、我们常用的多线程技术主要解决处理器单元内多个线程执行的问题,它的作用显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力。假设一个服务器完成一项任务所需时间为:T1 创建线程时间,T2 在线程中执行任务的时间,T3 销毁线程时间。在这种情况下经常会遇到T1+T3远远大于T2的情况,多线程反而成了负担。

为了解决这些痛点,出现了线程池这个概念。

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务加入队列,然后在线程创建后启动这些任务,如果先生超过了最大数量,超出的数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。

他的主要特点为:线程复用、控制最大并发数、管理线程。

第一:降低资源消耗,通过重复利用自己创建的线程降低线程创建和销毁造成的消耗。

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

第三: 提高线程的可管理性。线程是稀缺资源,如果无限的创建,不仅会消耗资源,还会较低系统的稳定性,使用线程池可以进行统一分配,调优和监控。

java自带的线程池工厂

java自带有一个线程池工厂,工厂里面的线程池分了如下几类:

Executors.newSingleThreadExecutor:创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

Executors.newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

Executors.newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

Executors.newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求

Executors.newWorkStealingPool:这个是jdk1.8新增的线程池,适合处理比较耗时的工作任务。

实际使用的线程池

既然java1.8自带了这么多线程池,我们平时生产中用那个呢?

抱歉,都不用。

为啥呢?

先看一眼线程池工作流程:

什么是线程池?线程池ThreadPoolExecutor使用及其原理又是什么?

 

如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

然后咱再看看阿里规约:

什么是线程池?线程池ThreadPoolExecutor使用及其原理又是什么?

 

喏,阿里规约写得很明白,这四个线程池有两个不限制队列长度,有两个不限制线程数,这在极高并发下是非常危险的,比如阿里的双十二,绝对秒炸。而且,没有合适的拒绝策略,虽然这四个要么不限制队列长,要么不限制线程数的线程池看起来都用不到,所以拒绝策略就是抛个异常就没了。

这不坑爹呢。

虽然对于非互联网公司貌似也够用,最起码简单省事发面快,咳咳,被小时候安琪酵母的广告洗脑了。

那我们自己写吧,看了看上面工厂生产的前四个线程池,貌似都是用下面这个基础函数生成的:

     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 线程池中的核心线程数maximumPoolSize 池中最大线程数keepAliveTime 当线程数超过核心线程数时,这个代表空线程在被终止前最长生存时间unit 最长存活时间的单位workQueue 线程池的工作队列,这个队列仅保存被Runnable任务execute方法提交的任务threadFactory 执行程序创建新线程时要使用的工厂(一般用默认Executors.defaultThreadFactory()即可)workQueue 拒绝策略,表示当线程队列满了并且工作线程大于等于线程池的最大显示数(maxnumPoolSize)时如何来拒绝.

拒绝策略又有啥呢?

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常。ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务ThreadPoolExecutor.CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

最初直观感受就是不能丢消息,用CallerRunsPolicy吧,主线程(暂且这么说吧,准确说是启动线程池的线程)跟着做业务。然后就发现,主线程一旦运行任务,即使线程池里的线程跑完任务都不会再进任务,因为主线程被占住了,直到主线程跑完一次业务,才能继续分配给线程池任务。问题很明显,业务流程比较耗时,线程池的一旦干完活,啥都干不了,都等着主线程消费队列的数据给新任务。其他三个策略,AbortPolicy直接抛异常,抛了能咋样,还是不知道要干啥;DiscardOldestPolicy丢弃老任务,丢消息,否了;DiscardPolicy丢弃,肯定否了。

我们要不然自己实现试试?

自己创建线程池

那我们尝试创建一个自己的线程池:

	int processors = Runtime.getRuntime().availableProcessors();
	int corePoolSize = processors+1;
	int maximumPoolSize = corePoolSize*2;
	ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
		corePoolSize,
		maximumPoolSize,
		5L,
		TimeUnit.MINUTES,
		new LinkedBlockingDeque<Runnable>(3),
		Executors.defaultThreadFactory(),
		(r, e) -> {
			if (!e.isShutdown()) {
				r.run();
            } else {
                LoggerFactory.getLogger(ThreadPoolTest.class).error("Task " + r.toString() + " rejected from " + e.toString());
            }
        }
	);

拒绝策略我简单写了点,在具体情况下是可以根据需要自己实现。

关于Java中Thread类的线程的小知识

我们在Thread类中,有一个State枚举类,为什么只有runnable 而没有running状态呢?

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

其实,这是因为现在的操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin式)。更复杂的可能还会加入优先级(priority)的机制。

这个时间分片通常是很小的,一个线程一次最多只能在 cpu 上运行比如10-20ms 的时间(此时处于 running 状态),时间片用后就要进行状态保存然后被切换下来放入调度队列的末尾等待再次调度(也即回到 ready 状态)。如果期间进行了 I/O 的操作还会导致提前释放时间分片,并进入等待队列;或者是时间分片没有用完就被抢占。

不计切换开销(每次在1ms 以内)的话,相当于1秒内有50-100次切换。事实上时间片经常没用完,线程就因为各种原因被中断,实际发生的切换次数还会更多。

时间分片也是可配置的,如果不追求在多个线程间很快的响应,也可以把这个时间配置得大一点,以减少切换带来的开销。

通常,Java的线程状态是服务于监控的,所以 Java 线程把调度委托给了操作系统,我们在虚拟机层面看到的状态实质是对底层状态的映射及包装。cpu 线程切换这么快,区分 ready 与 running 也没什么意义。因此,统一成为runnable 状态是不错的选择。

作者:Solid-Snaker

原文链接:https://blog.csdn.net/jcSongle/article/details/106089405



Tags:线程池   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
原文链接: https://mp.weixin.qq.com/s/MTw7z6n_wk4y4CTmGkoRoA一切要从CPU说起你可能会有疑问,讲多线程为什么要从CPU说起呢?原因很简单,在这里没有那些时髦的概念,你可以更加清...【详细内容】
2021-08-13  Tags: 线程池  点击:(96)  评论:(0)  加入收藏
多线程并发是Java语言中非常重要的一块内容,同时,也是Java基础的一个难点。说它重要是因为多线程是日常开发中频繁用到的知识,说它难是因为多线程并发涉及到的知识点非常之多,想...【详细内容】
2021-07-12  Tags: 线程池  点击:(110)  评论:(0)  加入收藏
1. Dubbo简介及线程池策略Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架。提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现...【详细内容】
2021-05-18  Tags: 线程池  点击:(202)  评论:(0)  加入收藏
在上一篇文章C++使用socket实现与微信小程序通信(下)中,小懵白就给大家简要地讲解了线程池的原理。 今天呢,小懵白就给大家继续讲解C++如何实现封装线程池类。第一步首先,我们需...【详细内容】
2021-05-14  Tags: 线程池  点击:(204)  评论:(0)  加入收藏
见字如面,我是威哥,一个从普通二本院校毕业,从未曾接触分布式、微服务、高并发到通过技术分享实现职场蜕变,成长为RocketMQ社区优秀布道师、大厂资深架构师,出版《RocketMQ技...【详细内容】
2021-03-31  Tags: 线程池  点击:(277)  评论:(0)  加入收藏
作者公众号:一角钱技术(org_yijiaoqian)前言线程池的具体实现有两种,分别是ThreadPoolExecutor 默认线程池和ScheduledThreadPoolExecutor 定时线程池,上一篇已经分析过ThreadPoo...【详细内容】
2020-12-22  Tags: 线程池  点击:(143)  评论:(0)  加入收藏
之前我们介绍了线程池的四种拒绝策略,了解了线程池参数的含义,那么今天我们来聊聊Java 中常见的几种线程池,以及在jdk7 加入的 ForkJoin 新型线程池 首先我们列出Java 中的...【详细内容】
2020-11-05  Tags: 线程池  点击:(91)  评论:(0)  加入收藏
前面几篇文章分析了线程的主要实现,今天来整体总结以下他们。总览图直接上总结的总览图,如下图: 如果看过前几篇文章应该基本能够看懂这张总结图,可能在单独的一篇文章里弄懂了...【详细内容】
2020-09-08  Tags: 线程池  点击:(63)  评论:(0)  加入收藏
大多数线程池实现都离不开锁的使用,如互斥量pthread_mutex*结合条件变量pthread_cond*。众所周知,锁的使用对于程序性能影响较大,虽然现有的pthread_mutex*在锁的申请与释放方...【详细内容】
2020-08-24  Tags: 线程池  点击:(87)  评论:(0)  加入收藏
作为 Java 程序员,无论是技术面试、项目研发或者是学习框架源码,不彻底掌握 Java 多线程的知识,做不到心中有数,干啥都没底气,尤其是技术深究时往往略显发憷。坐稳扶好,通过今天的...【详细内容】
2020-08-12  Tags: 线程池  点击:(48)  评论:(0)  加入收藏
▌简易百科推荐
为了构建高并发、高可用的系统架构,压测、容量预估必不可少,在发现系统瓶颈后,需要有针对性地扩容、优化。结合楼主的经验和知识,本文做一个简单的总结,欢迎探讨。1、QPS保障目标...【详细内容】
2021-12-27  大数据架构师    Tags:架构   点击:(3)  评论:(0)  加入收藏
前言 单片机开发中,我们往往首先接触裸机系统,然后到RTOS,那么它们的软件架构是什么?这是我们开发人员必须认真考虑的问题。在实际项目中,首先选择软件架构是非常重要的,接下来我...【详细内容】
2021-12-23  正点原子原子哥    Tags:架构   点击:(7)  评论:(0)  加入收藏
现有数据架构难以支撑现代化应用的实现。 随着云计算产业的快速崛起,带动着各行各业开始自己的基于云的业务创新和信息架构现代化,云计算的可靠性、灵活性、按需计费的高性价...【详细内容】
2021-12-22    CSDN  Tags:数据架构   点击:(10)  评论:(0)  加入收藏
▶ 企业级项目结构封装释义 如果你刚毕业,作为Java新手程序员进入一家企业,拿到代码之后,你有什么感觉呢?如果你没有听过多模块、分布式这类的概念,那么多半会傻眼。为什么一个项...【详细内容】
2021-12-20  蜗牛学苑    Tags:微服务   点击:(8)  评论:(0)  加入收藏
我是一名程序员关注我们吧,我们会多多分享技术和资源。进来的朋友,可以多了解下青锋的产品,已开源多个产品的架构版本。Thymeleaf版(开源)1、采用技术: springboot、layui、Thymel...【详细内容】
2021-12-14  青锋爱编程    Tags:后台架构   点击:(20)  评论:(0)  加入收藏
在了解连接池之前,我们需要对长、短链接建立初步认识。我们都知道,网络通信大部分都是基于TCP/IP协议,数据传输之前,双方通过“三次握手”建立连接,当数据传输完成之后,又通过“四次挥手”释放连接,以下是“三次握手”与“四...【详细内容】
2021-12-14  架构即人生    Tags:连接池   点击:(16)  评论:(0)  加入收藏
随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商...【详细内容】
2021-12-08  架构驿站    Tags:分布式系统   点击:(23)  评论:(0)  加入收藏
本系列为 Netty 学习笔记,本篇介绍总结Java NIO 网络编程。Netty 作为一个异步的、事件驱动的网络应用程序框架,也是基于NIO的客户、服务器端的编程框架。其对 Java NIO 底层...【详细内容】
2021-12-07  大数据架构师    Tags:Netty   点击:(16)  评论:(0)  加入收藏
前面谈过很多关于数字化转型,云原生,微服务方面的文章。虽然自己一直做大集团的SOA集成平台咨询规划和建设项目,但是当前传统企业数字化转型,国产化和自主可控,云原生,微服务是不...【详细内容】
2021-12-06  人月聊IT    Tags:架构   点击:(23)  评论:(0)  加入收藏
微服务看似是完美的解决方案。从理论上来说,微服务提高了开发速度,而且还可以单独扩展应用的某个部分。但实际上,微服务带有一定的隐形成本。我认为,没有亲自动手构建微服务的经历,就无法真正了解其复杂性。...【详细内容】
2021-11-26  GreekDataGuy  CSDN  Tags:单体应用   点击:(35)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条