线程池核心概述

线程池核心概述

  • Executors工厂类使用                        
  • Executors工厂类底层源码分析详解                
  • ThreadPoolExecutor自定义线程池                
  • ThreadPoolExecutor拒绝策略详解                
  • 计算机密集型与IO密集型详解            
  • 如何正确的使用线程池

线程池初步

  • 线程池,一般高并发其实是一个非常抽象的概念,要实现高并发其实不仅仅是一个JAVA 线程集合类、或者JAVA基础层面就能搞定的事情,在互联网大厂中,高并发其实涉及方方面面,从前端到后端,到支持高并发的中间组件(redis、zookeper等),最后到数据存储,持久化层面等等,都需要对高并发做一些考量和设计
  • 管理控制:首先,从管理角度就是为了更好的控制线程,使用线程池来帮助我们去管理线程,使得我们对线程的生命周期、初始化、运行状态、销毁等各个环节有一个把控
  • 系统资源:另外一点,从系统资源的角度考虑,线程池可以控制线程的数量,根据任务的多少去对线程池中的线程个数进行添加或者减少,可以回收空闲状态的线程,减少线程的频繁初始化和销毁,避免不必要的系统开销,节省系统资源,保障稳定性
  • 应用性能:从性能的角度去考虑,线程池可以配合高并发容器的设置,对任务和工作项进行缓存,异步的多线程的去处理任务,从而提高应用服务的吞吐率、消费性能,也从而提高单个线程的利用率
  • 兜底策略:从健壮性的角度去分析,线程池提供了很多拒绝策略,我们在任务过多或者处理不过来的时候,可以进行有效的拒绝策略、降级方案,以补偿的形式进行处理任务,避免因为线程池的问题对系统产生较为严重的影响

Executors

  • JDK提供了一套线程框架Executors,存储在java.util.concurrent包中,是JDK并发包的核心
  • Executors:线程工厂的角色,通过Executors可以创建特定功能的线程池

Executors创建线程池的方法

  • newFixedThreadPool()方法:该方法返回一个固定数量的线程池,该方法的线程数始终不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中等待有空闲的线程去执行
  • newSingleThreadPool ()方法:创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务列队中
  • newCachedThreadPool()方法:返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务,则创建线程,若无任务则不创建线程。如果没有任务则线程在60s后自动回收(空闲时间60s)
  • newScheduledThreadPool()方法:该方法返回一个SchededExecutorService对象,但该线程池可以指定线程的数量

自定义线程池ThreadPoolExecutor

  • 自定义线程池:若Executors工厂无法满足我们的需求,可以自己创建自定义线程池,其实Executors工厂类里面的创建线程方法其内部实现均是用了ThreadPoolExecutor这个类,这个类可以自定义线程。构造方法如下:
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
  • 使用有界队列:在使用有界队列时,若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程,若大于corePoolSize,则会将任务加入队列,若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程,若线程数大于maximumPoolSize,则执行拒绝策略。或其他自定义方式
  • 使用无界队列:在使用无界队列时:LinkedBlockingQueue。与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。当有新任务到来,系统的线程数小于corePoolSize时,则新建线程执行任务。当达到corePoolSize后,就不会继续增加。若后续仍有新的任务加入,而有没有空闲的线程资源,则任务直接进入队列等待。若任务创建和处理的速度差异很大,无界队列会保持快速增长,直到耗尽系统内存

线程池的拒绝策略

  • AbortPolicy:直接抛出异常阻止系统正常工作
  • CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的任务
  • DiscardOldestPolicy:丢弃最老的一个请求,尝试再次提交当前任务
  • DiscardPolicy:丢弃无法处理的任务,不给予任何处理
  • 如果需要自定义拒绝策略可以实现RejectedExecutionHandler接口
    //RejectedExecutionHandler接口
    public class MyRejected implements RejectedExecutionHandler{public MyRejected(){}@Overridepublic void rejectExecution(Runnable r,ThreadPoolExecutor executor){}
    }

如何使用好线程池

  • 线程个数大小的设置
  • 线程池相关参数配置
  • 利用Hook嵌入你的行为
  • 线程池的关闭

线程池大小设置(计算密集型/IO密集型)

  1. 计算密集型: 顾名思义就是应用需要非常多的CPU计算资源,在多核CPU时代,我们要让每一个CPU核心都参与计算,将CPU的性能充分利用起来,这样才算是没有浪费服务器配置,如果在非常好的服务器配置上还运行着单线程程序那将是多么重大的浪费。对于计算密集型的应用,完全是靠CPU的核数来工作,所以为了让它的优势完全发挥出来,避免过多的线程上下文切换。比较理想方案是: 线程数 = CPU核数+1,也可以设置成CPU核数*2,但还要看JDK的版本以及CPU配置(服务器的CPU有超线程)
  2. IO密集型: 就很好理解了,我们现在做的开发大部分都是WEB应用,涉及到大量的网络传输,不仅如此,与数据库,与缓存间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行。因此从这里可以发现,对于IO密集型的应用,我们可以多设置一些线程池中线程的数量,这样就能让在等待IO的这段时间内,线程可以去做其它事,提高并发处理效率。那么这个线程池的数据量是不是可以随便设置呢?当然不是的,请一定要记得,线程上下文切换是有代价的。目前总结了一套公式,对于IO密集型应用: 线程数 = CPU核心数/(1-阻塞系数) 这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。 套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整:final int poolSize = (int)(cpuCore/(1-0.9))

线程池相关参数配置注意事项

  1. 避免线上操作数据库,查询、修改都很麻烦
  2. 使用线程池的时候都不要选择没有上限限制的配置项
  3. 不要使用没有上限的线程池和设置无界队列
  4. newCachedThreadPool的设置与无界队列的设置因为某些不可预期的情况,线程池会出现系统异常,导致线程暴增的情况或者任务队列不断膨胀,内存耗尽导致系统崩溃和异常。 我们推荐使用自定义线程池来避免该问题,这也是在使用线程池规范的首要原则
  5. 合理设置线程数量、和线程空闲回收时间,根据具体的任务执行周期和时间去设定,避免频繁的回收和创建,虽然我们使用线程池的目的是为了提升系统性能和吞吐量,但是也要考虑下系统的稳定性,不然出现不可预期问题会很麻烦
  6. 根据实际场景,选择适用于自己的拒绝策略。进行补偿,不要乱用JDK支持的自动补偿机制!尽量采用自定义的拒绝策略去进行兜底

利用Hook嵌入你的行为

  1. 利用Hook,留下线程池执行轨迹
  2. 例如ThreadPoolExecutor提供了protected类型可以被覆盖的钩子方法,允许用户在任务执行之前会执行之后做一些事情。我们可以通过它来实现比如初始化ThreadLocal、收集统计信息、如记录日志等操作。这类Hook如beforeExecute和afterExecute。另外还有一个Hook可以用来在任务被执行完的时候让用户插入逻辑,如rerminated
  3. 如果hook方法执行失败,则内部的工作线程的执行将会失败或被中断

​​​​​​​关闭线程池

  1. 内容当线程池不在被引用并且工作线程数为0的时候,线程池将被终止。我们也可以调用shutdown来手动终止线程池。如果我们忘记调用shutdown,为了让线程资源被释放,我们还可以使用keepAliveTime和allowCoreThreadTimeOut来达到目的
  2. 当然,稳妥的方式是使用虚拟机Runtime.getRuntime().addShutdownHook方法,手工去调用线程池的关闭方法

相关代码

package com.bfxy.thread.core.pool;import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class UseThreadPoolExecutor {public static void main(String[] args) {ThreadPoolExecutor pool = new ThreadPoolExecutor(1,	// corePoolSize: 核心线程数,线程池初始化的时候就会被创建3,	// maximumPoolSize: 线程池的最大上限	//在使用无界队列的时候, 此参数 不起作用60,	//线程的存活时间TimeUnit.SECONDS,//workQueue:BlockingQueue接口下面的实现类//new ArrayBlockingQueue<>(2),	//使用有界队列: ArrayBlockingQueuenew LinkedBlockingQueue<>(),	//使用无界队列: LinkedBlockingQueuenew ThreadFactory() {	//threadFactory 线程工厂, 用于获取一个新的线程, 然后把该线程 投递到我们的线程池中去@Overridepublic Thread newThread(Runnable r) {Thread th = new Thread(r, "order-thread");if(th.getPriority() != Thread.NORM_PRIORITY) {th.setPriority(Thread.NORM_PRIORITY);}if(th.isDaemon()) {th.setDaemon(false);}return th;}},	//使用无界队列时, 拒绝策略不起到作用new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.err.println("当前的任务已经被拒绝: " + r.toString());}});Task t1 = new Task(1);Task t2 = new Task(2);Task t3 = new Task(3);Task t4 = new Task(4);Task t5 = new Task(5);Task t6 = new Task(6);/**//线程池提交任务的方法:pool.execute(t1);  		//execute: 如果你的任务没有返回值, 则使用该方法提交任务pool.submit(t1);		//submit: 如果你的任务有返回值, 则使用该方法提交任务, 返回一个Future对象(Future模式)*//*** * 在使用有界队列时:* 1 若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程* 2 若大于corePoolSize,则会将任务加入队列* 3 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程* 4 若线程数大于maximumPoolSize,则执行拒绝策略。*/// 1 若有新的任务需要执行,如果线程池实际线程数小于corePoolSize,则优先创建线程pool.execute(t1);	//core size = 1  t1任务会被核心线程执行// 2 若大于corePoolSize,则会将任务加入队列pool.execute(t2);	// 有界队列容量为: 2pool.execute(t3);// 3 若队列已满,则在总线程数不大于maximumPoolSize的前提下,创建新的线程, 并执行该任务pool.execute(t4);	// 线程池中的总线程数 2  , maximumPoolSize = 3 pool.execute(t5);	// 线程池中的总线程数 3  , maximumPoolSize = 3 // 4 若线程数大于maximumPoolSize,则执行拒绝策略。pool.execute(t6);pool.shutdown();}
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/447341.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网站盈利的10种方式

如果你有自己的网站&#xff0c;而且已经有了不少的流量&#xff0c;你肯定会开始考虑如何通过这个网站来挣一些钱。 在这篇文章中&#xff0c;我会向大家介绍网站最常见的10种盈利方式。 1.按点击付费广告 在网站上展示一个按点击付费的广告横幅是最简单的盈利方式&#xff…

程序员如何创业?

摘要&#xff1a;工作机会减少&#xff0c;读大学也不是保障。大公司亦不再是构筑职业生涯的安全港湾。透过媒体的镜头&#xff0c;创业似乎成了沙漠中唯一的绿洲。然而关于创业&#xff0c;或许少有人给你建议&#xff0c;这里所列出的一些因素都是你可以考虑的。 如果你的年…

Redis数据的类型

Redis一共分为五种基本数据类型&#xff1a;String、Hash、List、Set、Zset. string 内部编码有三种&#xff0c;raw&#xff0c;embstr&#xff0c;int String 是二进制的。可以存储序列化对象&#xff0c;图片&#xff0c;字符串&#xff0c;数值等 set和get方法 &#x…

Redis高级命令与特性以及单点模式的介绍

高级命令 keys * 返回满足条件的所有key&#xff0c;可以模糊匹配exists 是否存在指定的keypersist 取消过期时间select 选择数据库 &#xff08;0-15&#xff0c;总共16个数据库&#xff09;move key index 将当前数据库的 key 移动到给定的数据库 db 当中randomkey 随机返回…

华为副总裁徐家骏离职:年薪千万工作感悟十二条

从普通的公司职员&#xff0c;到年薪千万的华为副总裁&#xff0c;再到离开华为转战百度&#xff0c;徐家骏的十年从业经历和经验可资借鉴&#xff0c;我们从中也可以一窥华为的运作过程。徐家骏是华为数据中心的头&#xff0c;技术超级牛人&#xff0c;一级部门总监&#xff0…

Redis持久化之RDB和AOF

Redis持久化之RDB和AOF Redis 有两种持久化方案&#xff0c;RDB &#xff08;Redis DataBase&#xff09;和 AOF &#xff08;Append Only File&#xff09;&#xff1b; RDB 详解 RDB 是 Redis 默认的持久化方案。在指定的时间间隔内&#xff0c;执行指定次数的写操作&#…

同为程序员 为什么我的工资最低

我看着工资单上每一个开发团队成员的薪水&#xff0c;慢慢地我不能保持淡定了。 而当我看到我的工资排名是倒数的时候——靠近最后一名——我不由得倒抽一口冷气。就像圣诞故事中的那个可爱的小男孩Ralphie &#xff0c;想买气枪却被忽悠会有危险一样&#xff0c;我也不断忽悠…

Docker安装Redis以及配置Redis环境

1&#xff0c;下载Redis镜像 首先拉取 Redis 镜像, 这里我选择的是 redis:alpine 轻量级镜像版本 docker pull redis:alpine 下载完成后&#xff0c;通过 docker images 查看我们已经下载的镜像&#xff0c;看看是否已经下载到本地 2&#xff0c;运行 Redis 容器 docker run …

.NET程序性能的基本要领

摘要&#xff1a;本文分享了性能优化的一些建议和思考&#xff0c;比如不要过早优化、好工具很重要、性能的关键&#xff0c;在于内存分配等。开发者不要盲目的没有根据的优化&#xff0c;首先定位和查找到造成产生性能问题的原因点最重要。 【编者按】Bill Chiles&#xff08…

redis.conf配置文件详解

基本配置 daemonize no #是否以后台进程启动databases 16 #创建database的数量(默认选中的是database 0)save 900 1 #刷新快照到硬盘中&#xff0c;必须满足两者要求才会触发&#xff0c;即900秒之后至少1个关键字发生变化save 300 10 #必须是300秒之后至少10个关键字发生变…

什么原因成就了一位优秀的程序员?

这些年我曾和很多程序员一起工作&#xff0c;他们之中的一些人非常厉害&#xff0c;而另一些人显得平庸。不久前因为和一些技术非常熟练的程序员工作感觉很愉快&#xff0c;我花了一些时间在考虑我佩服他们什么呢&#xff1f;什么原因让优秀的程序员那么优秀&#xff0c;糟糕的…

Redis的哨兵模式Sentinel

sentinel功能 redis的sentinel系统用于管理多个redis服务器&#xff0c;该系统主要执行三个任务&#xff1a;监控、提醒、自动故障转移。 1、监控&#xff08;Monitoring&#xff09;&#xff1a; Redis Sentinel实时监控主服务器和从服务器运行状态&#xff0c;并且实现自动…

csdn 到底怎么了?不准转载?

我转载了20多文章&#xff0c;很多人阅读过&#xff0c;但是今天看到阅读量是0&#xff0c; csdn 到底怎么了&#xff1f; 对用户这样&#xff1f;请大家看看是不是这样&#xff1f;

python3之后版本读取网页的内容

import urllib.request url "http://helloworldbook2.com/data/message.txt" #直接通过url来获取网页数据 print(第一种) response urllib.request.urlopen(url) code response.getcode() html response.read() mystr html.decode("utf8") response.c…

三十功名尘与土——资深程序员生涯自白

摘要&#xff1a;作者Codist&#xff08;网名&#xff09;在程序员岗位上工作了三十多年&#xff0c;在这期间他悟出了一些真理&#xff0c;比如&#xff0c;成功来自对失败的总结学习&#xff1b;条条大路通罗马&#xff0c;罗马并不在乎你用什么方式到达。你在生活中积累了哪…

linux/unix核心设计思想

1&#xff09; 程序应该小而专一&#xff0c;程序应该尽量的小&#xff0c;且只专注于一件事上&#xff0c;不要开发那些看起来有用但是90%的情况都用不到的特性&#xff1b; 2&#xff09; 程序不只要考虑性能&#xff0c; 程序的可移植性更重要&#xff0c;shell和perl&…

操作系统环境变量

在 Java中&#xff0c;许多类都是 Iterable &#xff0c;主要包括所有的 Collection 类&#xff08;但不包括各种 Maps &#xff09;。 例如&#xff0c;下面的代码可以显示所有的操作系统环境变量&#xff1a; // collections/EnvironmentVariables.java // {VisuallyInspect…

用Unix的设计思想来应对多变的需求

摘要&#xff1a;无论是Unix设计&#xff0c;还是面向对象设计&#xff0c;还是别的什么如SOA&#xff0c;ECB&#xff0c;消息&#xff0c;事件&#xff0c;MVC&#xff0c;网络七层模型&#xff0c;数据库设计&#xff0c;等等&#xff0c;他们都在干三件事——解耦&#xff…

学习较底层编程:动手写一个C语言编译器

动手编写一个编译器&#xff0c;学习一下较为底层的编程方式&#xff0c;是一种学习计算机到底是如何工作的非常有效方法。 编译器通常被看作是十分复杂的工程。事实上&#xff0c;编写一个产品级的编译器也确实是一个庞大的任务。但是写一个小巧可用的编译器却不是这么困难。…

Arrays.deepToString() 方法同时适用于基元数组和对象数组

Arrays.deepToString() 方法同时适用于基元数组和对象数组&#xff1a; import java.util.*;public class MultiDimWrapperArray {public static void main(String[] args) {Integer[][] a1 { // Autoboxing{ 1, 2, 3, },{ 4, 5, 6, },};Double[][][] a2 { // Autoboxing{ {…