线程池-线程池的好处

article/2025/9/28 23:27:09

1.线程池的好处。

线程使应用能够更加充分合理的协调利用cpu 、内存、网络、i/o等系统资源。

线程的创建需要开辟虚拟机栈,本地方法栈、程序计数器等线程私有的内存空间。

在线程的销毁时需要回收这些系统资源。频繁的创建和销毁线程会浪费大量的系统资源,增加并发编程的风险。

另外,在服务器负载过大的时候,如何让新的线程等待或者友好的拒绝服务?这些丢失线程自身无法解决的。所以需要通过线程池协调多个线程,并实现类似主次线程隔离、定时执行、周期执行等任务。线程池的作用包括:

  • 利用线程池管理并复用线程、控制最大并发数等。
  • 实现任务线程队列缓存策略和拒绝机制。
  • 实现某些与时间相关的功能,如定时执行、周期执行等。
  • 隔离线程环境。比如,交易服务和搜索服务在同一台服务器上,分别开启两个线程池,交易线程的资源消耗明显要大;因此,通过配置独立的线程池,将较慢的交易服务与搜索服务隔开,避免个服务线程互相影响。

在了解线程池的基本作用后,我们学习一下线程池是如何创建线程的。首先从ThreadPoolExecutor构造方法讲起,学习如何定义ThreadFectory和RejectExecutionHandler,并编写一个最简单的线程池示例。然后,通过分析ThreadPoolExecutor的execute和addWorker两个核心方法,学习如何把任务线程加入到线程池中运行。ThreadPoolExecutor的构造方法如下:

 1 public ThreadPoolExecutor(
 2                               int corePoolSize,        //第1个参数
 3                               int maximumPoolSize, //第2个参数
 4                               long keepAliveTime, //第3个参数
 5                               TimeUnit unit, //第4个参数
 6                               BlockingQueue<Runnable> workQueue, //第5个参数
 7                               ThreadFactory threadFactory, //第6个参数
 8                               RejectedExecutionHandler handler) { //第7个参数
 9         if (corePoolSize < 0 ||
10             maximumPoolSize <= 0 || 
11             maximumPoolSize < corePoolSize || 
12             keepAliveTime < 0) // 第一处
13             throw new IllegalArgumentException();
14         if (workQueue == null || threadFactory == null || handler == null)//第二处
15             throw new NullPointerException();
16         this.corePoolSize = corePoolSize;
17         this.maximumPoolSize = maximumPoolSize;
18         this.workQueue = workQueue;
19         this.keepAliveTime = unit.toNanos(keepAliveTime);
20         this.threadFactory = threadFactory;
21         this.handler = handler;
22     }            
  • 第1个参数 :corePoolSize 表示常驻核心线程数。如果等于0,则任务执行完成后,没有任何请求进入时销毁线程池的线程;如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。这个值的设置非常关键,设置过大会浪费资源,设置的过小会导致线程频繁地创建或销毁。
  • 第2个参数:maximumPoolSize 表示线程池能够容纳同时执行的最大线程数。从上方的示例代码中第一处来看,必须大于或等于1。如果待执行的线程数大于此值,需要借助第5个参数的帮助。缓存在队列中。如果maximumPoolSize 与corePoolSize 相等,即是固定大小线程池。
  • 第3个参数:keepAliveTime 表示线程池中的线程空闲时间,当空闲时间达到KeepAliveTime 值时,线程被销毁,直到剩下corePoolSize 个线程为止,避免浪费内存和句柄资源。在默认情况下,当线程池的线程大于corePoolSize 时,keepAliveTime 才会起作用。但是ThreadPoolExecutor的allowCoreThreadTimeOut 变量设置为ture时,核心线程超时后也会被回收。
  • 第4个参数:TimeUnit 表示时间单位。keepAliveTime 的时间单位通常是TimeUnit.SECONDS。
  • 第5个参数:   workQueue 表示缓存队列。当请求的线程数大于maximumPoolSize时,线程进入BlockingQueue 阻塞队列。后续示例代码中使用的LinkedBlockingQueue 是单向链表,使用锁来控制入队和出对的原子性,两个锁分别控制元素的添加和获取,是一个生产消费模型队列。
  • 第6个参数:threadFactory 表示线程工厂。它用来生产一组相同任务的线程。线程池的命名是通过给这个factory增加组名前缀来实现的。在虚拟机栈分析时,就可以知道线程任务是由哪个线程工厂产生的。
  • 第7个参数:handler 表示执行拒绝策略的对象。当超过第5个参数workQueue的任务缓存区上限的时候,就可以通过该策略处理请求,这是一种简单的限流保护。友好的拒绝策略可以使如下三种:
  1. 保存到数据库进行削峰填谷。在空闲的时候再拿出来执行。
  2. 转向某个提示页面。
  3. 打印日志。

从代码第2处来看,队列、线程工程、拒绝处理服务都必须有实例对象,但在实际编码中,很少有程序员对着三者进行实例化,而通过Executors这个线程池静态工厂提供默认实现,那么Executors与ThreadPoolExecutor 是什么关系呢?线程池相关的类图

ExecutorService接口继承了Executor接口,定义了管理线程任务的方法。ExecutorService 的抽象类AbstractExecutorService 提供了 submit()、invokeAll()等部分方法的实现,但是核心方法Executor.execute() 并没有在这里实现。因为所有的任务都在这个方法里执行,不同的实现会带来不同的执行策略,这一点在后续的ThreadPoolExecutor解析时,会一步步分析。通过Executor的静态工厂方法可以创建三个线程池的包装对象:ForkJoinPool、ThreadPoolExecutor、ScheduledThreadPoolExecutor。Executors核心方法有5个:

  • Executors.newWorkStealingPool:JDK8 引入,创建持有足够线程的线程池,支持给定的并行堵,并通过使用对个队列减少竞争,此构造方法中把cpu的数量设置为模型的并行度:
 1 /**
 2      * Creates a work-stealing thread pool using all
 3      * {@link Runtime#availableProcessors available processors}
 4      * as its target parallelism level.
 5      * @return the newly created thread pool
 6      * @see #newWorkStealingPool(int)
 7      * @since 1.8
 8      */
 9     public static ExecutorService newWorkStealingPool() {
10         return new ForkJoinPool
11             (Runtime.getRuntime().availableProcessors(),
12              ForkJoinPool.defaultForkJoinWorkerThreadFactory,
13              null, true);
14     }
  • Executors.newCachedThreadPool : maximumPoolSize 最大可以至Integer.MAX_VALUE,是高度可以伸缩的线程池,如果达到这个上限,相信没有任何服务器能够继续工作,肯定会抛出OOM异常。keepAliveTime默认为60秒,工作线程处于空闲状态,则回收工作线程。如果任务书增加,再次创建新的线程处理任务。
  • Executors.new ScheduledThreadPool: 线程最大至Integer.MAX_VALUE ,与上述相同,存在OOM风险。它是ScheduledExecutorService 接口家族的实现类,支持定时及周期性任务执行。相比Timer,ScheduledExextuorService 更安全,功能更强大,与newCachedThreadExecutor 的区别是不回收工作线程。
  • Executors.newSingleThreadExecutor:创建一个单线程的线程池,相当月单线程串行执行所有任务,保证按任务提交的顺序依次执行。
  • Executors.newFixedThreadPool: 输入的参数即是固定线程数,既是核心线程数也是最大线程数,不存在空闲线程,所有keepAliveTime 等于0:
    1  public static ExecutorService newFixedThreadPool(int nThreads) {
    2         return new ThreadPoolExecutor(nThreads, nThreads,
    3                                       0L, TimeUnit.MILLISECONDS,
    4                                       new LinkedBlockingQueue<Runnable>());
    5     }

    这里,输入的队列没有指明长度,下面介绍LinkedBlockingQueue的构造方法。

    1 public LinkedBlockingQueue() {
    2         this(Integer.MAX_VALUE);
    3     }

    使用这样的无界队列,如果瞬间请求非常大,会有OOM的风险。除newWorkStealingPool 外,其他四个创建方式都存在资源耗尽的风险。

Executors 中默认的线程工程和拒绝策略过于简单,通常对用户不够友好。线程工厂需要做创建前的准备工作,对线程池创建的线程必须明确标识,就像药品的生产批号一样,为线程本身指定有意思的名称和相应的序列号。拒绝策略应该考虑到业务场景返回相应的提示或者友好的跳转 1 public class UserThreadFactory implements ThreadFactory { 2 private final String namePrefix;

 3     private final AtomicInteger nextId = new AtomicInteger();
 4     //定义线程组名称,在使用jstack 来排查问题是,非常有帮助
 5     UserThreadFactory(String whatFeatureOfGroup){
 6         namePrefix = "UserThreadFactory's"+whatFeatureOfGroup+"-Worker-";
 7     }
 8 
 9     @Override
10     public Thread newThread(Runnable task){
11         String name = namePrefix+ nextId.getAndIncrement();
12         Thread thread = new Thread(null,task,name,0);
//打印threadname
13 return thread; 14 } 15 16 public static void main(String[] args) { 17 UserThreadFactory threadFactory = new UserThreadFactory("你好"); 18 String ss = threadFactory.namePrefix; 19 Task task = new Task(ss); 20 Thread thread = null; 21 for(int i = 0; i < 10; i++) { 22 thread = threadFactory.newThread(task); 23 thread.start(); 24 } 25 26 } 27 } 28 29 class Task implements Runnable{ 30 String poolname; 31 Task(String poolname){ 32 this.poolname = poolname; 33 } 34 private final AtomicLong count = new AtomicLong(); 35 36 @Override 37 public void run(){ 38 System.out.println(poolname+"_running_"+ count.getAndIncrement()); 39 } 40 }

上述示例包括线程工厂和任务执行体的定义,通过newThread 方法快速、统一地创建线程任务,强调线程一定要是欧特定的意义和名称,方便出错时回溯。

下面简单地实现一下RejectedExecutionHandler,实现了接口的rejectExecution方法,打印当前线程池状态,源码如下。

1 public class UserRejectHandler implements RejectedExecutionHandler {
2     
3     @Override
4     public void rejectedExecution(Runnable task, ThreadPoolExecutor executor){
5         System.out.println("task rejected."+ executor.toString());
6     }
7 }

在ThreadPoolExecutor 中提供了4个公开的内部静态类:

  • AbortPolicy (默认):丢弃任务并抛出RejectExecutionException 异常。
  • DiscardPolicy:丢弃任务,但是不抛出异常,这是不推荐的做法。
  • DiscardOldestPolicy : 抛弃队列中等待最久的任务,然后把当前任务加入队列中。
  • CallerRunsPolicy : 调用任务的run方法绕过线程池直接执行。

根据之前实现的线程工厂和拒绝策略,线程池的相关代码实现如下:

public class UserThreadPool {public static void main(String[] args) {BlockingQueue queue = new LinkedBlockingQueue(2);UserThreadFactory f1 = new UserThreadFactory("第一机房");UserThreadFactory f2 = new UserThreadFactory("第二机房");UserRejectHandler handler = new UserRejectHandler();ThreadPoolExecutor threadPoolExecutor =new ThreadPoolExecutor(1,2,60,TimeUnit.SECONDS,queue,f1,handler);ThreadPoolExecutor threadPoolExecutor2 =new ThreadPoolExecutor(1,2,60,TimeUnit.SECONDS,queue,f2,handler);Runnable task1 = new Task("");for (int i = 0;i<200;i++){threadPoolExecutor.execute(task1);threadPoolExecutor2.execute(task1);}}
}

当任务被拒绝的时候,拒绝策略会打印出当前线程池的大小以及达到了maximumPoolSize=2 ,且队列已满。

 

转载于:https://www.cnblogs.com/WangJinYang/p/10226866.html


http://chatgpt.dhexx.cn/article/WAnJH1Xn.shtml

相关文章

什么是线程池,线程池的作用

线程池&#xff0c;--其实就是一个 容纳多个线程的容器 &#xff0c;其中的线程可以反复使用&#xff0c;省去了频繁创建线程对象的操作 &#xff0c;--无需反复创建线程而消耗过多资源。 创建销毁线程是一个非常消耗性能的。 我们详细的解释一下为什么要使用线程池&#xff1f…

华为防火墙实战配置教程,太全了

防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙是系统的第一道防线&#xff0c;其作用是防止非法用户的进入。 本期我们一起来总结下防火墙的配置&#xff0c;非常全面&#xff0c;以华为为例。 防火墙的配置…

防火墙配置(命令)

拓扑图&#xff1a; 目的&#xff1a;PC1和PC2相互ping通。 配置命令&#xff1a; FW1: //添加端口IP [SRG-GigabitEthernet0/0/0]interface GigabitEthernet0/0/1 [SRG-GigabitEthernet0/0/1]ip add 192.168.1.1 24 [SRG]inter g0/0/2 [SRG-GigabitEthernet0/0/2]ip add 10.…

防火墙基础配置(二)

拓补图&#xff1a; 方案一&#xff08;子接口的形式&#xff09; 实验目的&#xff1a;解决防火墙上的接口不足以为其他区域服务的问题&#xff0c;比方说防火墙上只有两个接口&#xff0c;但是有三个区域&#xff0c;那这个实验的目的为了解决防火墙上的接口不足以为多区域提…

H3C防火墙基础配置2-配置安全策略

1 安全策略简介 安全策略对报文的控制是通过安全策略规则实现的&#xff0c;规则中可以设置匹配报文的过滤条件&#xff0c;处理报文的动作和对于报文内容进行深度检测等功能。 &#xff08;1&#xff09;规则的名称和编号 安全策略中的每条规则都由唯一的名称和编号标识。名称…

H3C防火墙-安全域配置举例

1. 组网需求 某公司以 Device 作为网络边界安全防护设备&#xff0c;连接公司内部网络和 Internet。公司只对内部提供Web 服务&#xff0c;不对外提供这些服务。现需要在设备上部署安全域&#xff0c;并基于以下安全需求进行域间策略 的配置。 • 与接口 GigabitEthernet1/0/1 …

Firewalld防火墙实例配置

文章目录 环境拓扑需求描述一、环境配置二、防火墙配置1、在网站服务器上配置防火墙2、网关服务器配置防火墙3、企业内网访问外网web服务器4、外网web服务器访问企业内部网站服务器 三、总结问题总结解决方案 环境拓扑 需求描述 1、 网关服务器连接互联网网卡ens33地址为100.1…

【Linux】配置网络和firewall防火墙(超详细介绍+实战)

&#x1f947;&#x1f947;【Liunx学习记录篇】&#x1f947;&#x1f947; 篇一&#xff1a;【Linux】VMware安装unbuntu18.04虚拟机-超详细步骤(附镜像文件&#xff09; 篇二&#xff1a;【Linux】ubuntu18.04系统基础配置及操作 篇三&#xff1a;【Linux】用户与组的操作详…

防火墙配置

防火墙&#xff08;Firewall&#xff09;&#xff0c;也称防护墙。它是一种位于内部网络与外部网络之间的网络安全系统。一项信息安全的防护系统&#xff0c;依照特定的规则&#xff0c;允许或是限制传输的数据通过。防火墙对于我们的网络安全的重要性不言而喻 但是在实际的开发…

H3C防火墙-安全策略典型配置举例

基于 IP 地址的安全策略配置举例 1.组网需求 • 某公司内的各部门之间通过 Device 实现互连&#xff0c;该公司的工作时间为每周工作日的 8 点到 18点。 • 通过配置安全策略规则&#xff0c;允许总裁办在任意时间、财务部在工作时间通过 HTTP 协议访问财务数据库服务器的 Web…

华为防火墙配置教程

01 了解防火墙基本机制 配置防火墙之前请了解防火墙基本工作机制。 1.1 什么是防火墙 防火墙是一种网络安全设备&#xff0c;通常位于网络边界&#xff0c;用于隔离不同安全级别的网络&#xff0c;保护一个网络免受来自另一个网络的攻击和入侵。这种“隔离”不是一刀切&#x…

防火墙基本配置

防火墙 种类 1.包过滤技术 「 静态防火墙 动态防火墙 」 netfilter 真正的配置 位于linux内核的包过滤功能体系 称为linux防火墙的“内核态” iptables 防火墙的配置 工具 主要针对 网络层 针对IP数据包 「体现在对包的IP地址、端口等信息处理」 链表结构 链 ---- 容纳 规则…

防火墙详解(三)华为防火墙基础安全策略配置(命令行配置)

实验要求 根据实验要求配置防火墙&#xff1a; 合理部署防火墙安全策略以及安全区域实现内网用户可以访问外网用户&#xff0c;反之不能访问内网用户和外网用户均可以访问公司服务器 实验配置 步骤一&#xff1a;配置各个终端、防火墙端口IP地址 终端以服务器为例&#x…

防火墙基础配置

状态防火墙 状态检测防火墙&#xff08;Stateful Firewall&#xff09;是一种网络安全设备&#xff0c;它可以检测和过滤网络流量&#xff0c;以保护网络不受未经授权的访问和攻击。 与传统的包过滤防火墙不同&#xff0c;状态检测防火墙可以跟踪网络连接的状态&#xff0c;并…

10分钟教你完全掌握防火墙配置!!!!!

今日提问 1.防火墙支持那些NAT技术&#xff0c;主要应用场景是什么&#xff1f; 2.当内网PC通过公网域名解析访问内网服务器时&#xff0c;会存在什么问题&#xff0c;如何解决&#xff1f;请详细说明 3.防火墙使用VRRP实现双机热备时会遇到什么问题&#xff0c;如何解决&…

H3C简单的防火墙配置

这里写目录标题 实验拓扑实验需求配置过程1.配置ip地址&#xff08;略&#xff09;2.配置去往公网的默认路由3.将端口绑定在信任域和不信任域4.配置ipv4安全模板5.配置ospf将内网的连通性完成6.配置nat &#xff08;easy ip的方式&#xff09;使内网PC可以访问外网 测试 实验拓…

防火墙的基础配置(一)

拓补图&#xff1a; 注意事项&#xff1a; 不要连接着防火墙的g0/0/0口&#xff0c;这个口是防火墙的管理端口一定要将接口划分区域&#xff0c;防火墙有四个区域&#xff0c;分别是local、trust、untrust、dmz&#xff0c;优先级分别是100、85、50、5&#xff0c;优先级越高说…

三、防火墙配置(1)---防火墙常规配置

一、实验内容 1、在GNS中创建如下图所示的网络拓扑结构。 2、给路由器和防火墙按照拓扑图中的规划&#xff0c;配置好IP地址和路由表。给R1、R2、R4、R6开启远程连接。3、验证防火墙默认安全规则&#xff0c;高安全级别接口&#xff08;inside&#xff09;可主动访问低安全级别…

华为防火墙网管配置实例

今天给大家带来华为USG6000防火墙的网管配置实例。本文简单的搭建了一个实验拓扑图&#xff0c;通过配置&#xff0c;实现了对华为防火墙的Telnet管理配置、SSH管理配置和Web管理配置。 一、实验拓扑及目的 实验拓扑如上所示&#xff0c;要求对防火墙FW1进行合适的配置&#x…

华为防火墙实战配置教程

防火墙&#xff08;Firewall&#xff09;也称防护墙&#xff0c;是由Check Point创立者Gil Shwed于1993年发明并引入国际互联网&#xff08;US5606668&#xff08;A&#xff09;1993-12-15&#xff09;防火墙是位于内部网和外部网之间的屏障&#xff0c;它按照系统管理员预先定…