线程池原理总结

article/2025/10/7 9:29:15

【引言】

关于线程池,印象中好像自己有写过相关的总结博客。翻了翻之前的博客,确实,在去年十一月写过一篇《线程池使用总结》。

时隔一年,我已经离开了那家让我成长很多的公司,在那里,写了很多的代码,学了很多的技术。而现在的我,每天在公司都要面试招人,让我又有了加深理论学习的机会。所以,本篇博客内容总结线程池的原理。

线程池处理流程

当向线程池提交一个任务之后,线程池是如何处理这个任务的呢?线程池的处理流程如下图所示:
在这里插入图片描述

  1. 线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下一个流程。

  2. 线程池判断工作队列是否已经满。如果工作队列没有满,则将新提交的任务存储在这个工作队列里。如果工作队列满了,则进入下个流程。

  3. 线程池判断线程池里的线程是否都处于工作状态。如果没有,则创建一个新的线程来执行此任务。如果已经满了,则交给饱和策略来处理任务。

在ThreadPoolExecutor类中,最核心的任务提交方法是execute()方法,执行示意图如下:

在这里插入图片描述

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(这一过程,需要获取全局锁)

  2. 如果运行的线程等于或多于corePoolSize,则将任务假如BlockingQueue。

  3. 如果无法加入BlockQueue(任务队列已满),则创建新的线程来处理任务(这一过程,需要获取全局锁)

  4. 如果创建新线程将使当前运行的线程超过maxmumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

线程池核心代码

1. execute方法

public void execute(Runnable command) {
//判断提交的任务command是否为null,若是null,则抛出空指针异常;if (command == null)throw new NullPointerException();
//如果线程数小于基本线程数,则创建线程并执行当前任务if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
//如果线程数大于基本线程数或线程创建失败,则将当前任务放到工作队列中if (runState == RUNNING && workQueue.offer(command)) {if (runState != RUNNING || poolSize == 0)ensureQueuedTaskHandled(command);}
//如果线程池不处于运行中或任务无法放入队列,并且当前线程数量小于最大允许的线程数量,则创建一个线程执行任务else if (!addIfUnderMaximumPoolSize(command))reject(command); // is shutdown or saturated}
}

2. addIfUnderCorePoolSize方法

//当低于核心池大小时执行的方法
private boolean addIfUnderCorePoolSize(Runnable firstTask) {Thread t = null;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (poolSize < corePoolSize && runState == RUNNING)t = addThread(firstTask);      //创建线程去执行firstTask任务   } finally {mainLock.unlock();}
//判断t是否为空,为空则表明创建线程失败(即poolSize>=corePoolSize或者runState不等于RUNNING),否则调用t.start()方法启动线程。if (t == null)return false;t.start();return true;
}

3. addThread方法

//参数为提交的任务,返回值为Thread类型
private Thread addThread(Runnable firstTask) {
//创建一个Worker对象Worker w = new Worker(firstTask);Thread t = threadFactory.newThread(w);  //创建一个线程,执行任务   if (t != null) {w.thread = t;            //将创建的线程的引用赋值为w的成员变量       workers.add(w);int nt = ++poolSize;     //当前线程数加1       if (nt > largestPoolSize)largestPoolSize = nt;}return t;
}

4. Worker对象

private final class Worker implements Runnable {private final ReentrantLock runLock = new ReentrantLock();private Runnable firstTask;volatile long completedTasks;Thread thread;Worker(Runnable firstTask) {this.firstTask = firstTask;}boolean isActive() {return runLock.isLocked();}void interruptIfIdle() {final ReentrantLock runLock = this.runLock;if (runLock.tryLock()) {try {if (thread != Thread.currentThread())thread.interrupt();} finally {runLock.unlock();}}}void interruptNow() {thread.interrupt();}private void runTask(Runnable task) {final ReentrantLock runLock = this.runLock;runLock.lock();try {if (runState < STOP &&Thread.interrupted() &&runState >= STOP)boolean ran = false;beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据//自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等           try {task.run();ran = true;afterExecute(task, null);++completedTasks;} catch (RuntimeException ex) {if (!ran)afterExecute(task, ex);throw ex;}} finally {runLock.unlock();}}//首先执行的是通过构造器传进来的任务firstTask,在调用runTask()执行完firstTask之后,在while循环里面不断通过getTask()去缓存队列中取新的任务来执行public void run() {try {Runnable task = firstTask;firstTask = null;while (task != null || (task = getTask()) != null) {runTask(task);task = null;}} finally {workerDone(this);   //当任务队列中没有任务时,进行清理工作       }}
}

5. ThreadPoolExecutor类中的getTask方法:

Runnable getTask() {for (;;) {try {int state = runState;//判断当前线程池状态,如果runState大于SHUTDOWN(即为STOP或者TERMINATED),则直接返回nullif (state > SHUTDOWN)return null;Runnable r;//如果线程状态为SHUTDOWN,则从缓存队列中取任务if (state == SHUTDOWN)  // Help drain queuer = workQueue.poll();else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,//则通过poll取任务,若等待一定的时间取不到任务,则返回nullr = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);elser = workQueue.take();if (r != null)return r;if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出if (runState >= SHUTDOWN) // Wake up othersinterruptIdleWorkers();   //中断处于空闲状态的workerreturn null;}// Else retry} catch (InterruptedException ie) {// On interruption, re-check runState}}
}

6. workerCanExit方法

//如果runState大于等于STOP,或者任务缓存队列为空了,或者允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1,允许worker退出
private boolean workerCanExit() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();boolean canExit;try {canExit = runState >= STOP ||workQueue.isEmpty() ||(allowCoreThreadTimeOut &&poolSize > Math.max(1, corePoolSize));} finally {mainLock.unlock();}return canExit;
}

7. interruptldleWorkers方法

//如果允许worker退出,则调用该方法中断处于空闲状态的worker
void interruptIdleWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers)  //实际上调用的是worker的interruptIfIdle()方法w.interruptIfIdle();} finally {mainLock.unlock();}
}void interruptIfIdle() {final ReentrantLock runLock = this.runLock;if (runLock.tryLock()) {    //调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的//如果成功获取了锁,说明当前worker处于空闲状态try {if (thread != Thread.currentThread())  thread.interrupt();} finally {runLock.unlock();}}
}

8. addIfUnderMaximumPoolSize方法

//线程池中的线程数达到了核心池大小并且往任务队列中添加任务失败的情况下执行
private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {Thread t = null;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (poolSize < maximumPoolSize && runState == RUNNING)t = addThread(firstTask);} finally {mainLock.unlock();}if (t == null)return false;t.start();return true;
}

在上面多次提到的任务队列,即workQueue,用来存放等待执行的任务,类型为BlockQueue,在《线程池使用总结》文章中有写到,这里就不再列举了,下面总结下饱和策略。

线程池饱和策略:

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  1. ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。

  2. ThreadPoolExecutor.DiscardPolicy:丢弃任务,但不抛出异常

  3. ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)

  4. ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

【总结】

关于线程池的总结,使用+原理,这下全面了。

时隔一年,又写到了线程池,博客记录成长,也记录生活。

今天的朋友圈被帝都二零一九年的初雪刷屏了,这场雪,相比去年,是早了些,二零二零的脚步也越来越近了。


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

相关文章

线程池原理全解析

目录 1 线程池简介 2 线程池 2.1 ThreadPoolExecutor类 2.2 ThreadPoolExecutor方法 3 线程池实现原理 3.1.线程池状态 3.2.任务的执行 总结过程 3.3.线程池中的线程初始化 3.4.任务缓存队列及排队策略 3.5.任务拒绝策略 3.6.线程池的关闭 3.7.线程池容量的动态调…

一文带你清晰弄明白线程池的原理

不知道你是否还记得阿里巴巴的java代码规范中对多线程有这样一条强制规范: 【强制】线程资源必须通过线程池提供&#xff0c;不允许在程序中显示创建线程。 说明&#xff1a;使用线程池的好处是减少在创建和销毁线程池上所消耗的时间以及系统资源的开销&#xff0c;解决资源不足…

线程池工作原理

一、线程池默认工作流程 1、线程在有任务的时候会创建核心的线程数corePoolSize 2、当线程满了&#xff08;有任务但是线程被使用完&#xff09;不会立即扩容,而是放到阻塞队列中,当阻塞队列满了之后才会继续创建线程。 3、如果队列满了,线程数达到最大线程数则会执行拒绝策…

线程池的工作原理

线程池&#xff0c;就是存放线程的池子&#xff0c;池子里存放了很多可以复用的线程 作用&#xff1a; 1.对线程进行统一管理 2.降低系统资源消耗。通过复用已存在的线程&#xff0c;降低线程创建和销毁造成的消耗 3.提高响应速度。当有任务到达时&#xff0c;无需等待新线…

线程池核心原理分析

一、基础概念 线程池是一种多线程开发的处理方式&#xff0c;线程池可以方便得对线程进行创建&#xff0c;执行、销毁和管理等操作。主要用来解决需要异步或并发执行任务的程序 谈谈池化技术 简单点来说,就是预先保存好大量的资源,这些是可复用的资源,你需要的时候给你。对于…

线程池原理(讲的非常棒)

Java并发编程&#xff1a;线程池的使用 在前面的文章中&#xff0c;我们使用线程的时候就去创建一个线程&#xff0c;这样实现起来非常简便&#xff0c;但是就会有一个问题&#xff1a; 如果并发的线程数量很多&#xff0c;并且每个线程都是执行一个时间很短的任务就结束了&…

服务器常见的网络攻击以及防御方法

网络安全威胁类别 网络内部的威胁&#xff0c;网络的滥用&#xff0c;没有安全意识的员工&#xff0c;黑客&#xff0c;骇客。 木马攻击原理 C/S 架构&#xff0c;服务器端被植入目标主机&#xff0c;服务器端通过反弹连接和客户端连接。从而客户端对其进行控制。 病毒 一…

传奇服务器容易受到什么攻击,怎么防御攻击?

有兄弟问明杰&#xff0c;说自己打算开服&#xff0c;听说攻击挺多的&#xff0c;就是想先了解一下开传奇用的服务器最容易受到什么类型的攻击&#xff0c;如果遇到了又改怎么防御呢&#xff1f;带着这个问题&#xff0c;明杰跟大家详细的说一下&#xff0c;常见的开区时候遇到…

传奇服务器常见的网络攻击方式有哪些?-版本被攻击

常见的网络攻击方式有哪些&#xff1f;常见的网络攻击方式 &#xff1a;端口扫描&#xff0c;安全漏洞攻击&#xff0c;口令入侵&#xff0c;木马程序&#xff0c;电子邮件攻击&#xff0c;Dos攻击。 1.传奇服务器端口扫描&#xff1a; 通过端口扫描可以知道被扫描计算机开放了…

服务器被ddos攻击的处置策略

如果您的服务器遭到了DDoS攻击&#xff0c;以下是一些可以采取的措施&#xff1a; 使用防火墙和安全组进行限制&#xff1a;限制服务器的流量以防止进一步的攻击。 升级服务器资源&#xff1a;为了应对更高的流量&#xff0c;可以升级服务器的内存&#xff0c;处理器等资源。 安…

如何从根本上防止服务器被攻击

随着互联网的发展&#xff0c;服务器成为了企业和个人网络应用的重要基础设施。但是&#xff0c;随之而来的网络安全威胁也在不断增加&#xff0c;服务器安全问题也成为了影响企业信息安全的重要因素。针对这种情况&#xff0c;服务器安全防御变得尤为重要。   服务器安全防御…

来一起学怎么攻击服务器吧!!!

我们上线了两门新课。 一门教你漏洞的原理和攻击代码怎么写。 一门会教你木马的制作。 一如既往地&#xff0c;新课刚上线都是限时优惠状态&#xff0c;感兴趣的同学用力点击下面这两个妹子吧&#xff1a; Kali 渗透测试 - 服务器攻击实战 Kali 渗透测试 - 后门技术实战 提示&a…

网站服务器怎么做防御?遇到攻击如何解决?

每个网站都可能遇到网络攻击&#xff0c;这是正常现象。攻击可能来自密码错误、防火墙配置错误、病毒软件、空闲端口等。大量的网络攻击在竞争激烈的行业或企业中更为常见。 目前需要有一套完整的安全策略来保护流浪数据&#xff0c;防止网站服务器被攻击。完善可靠的安全策略…

服务器被攻击怎么办?常见处理方法

对于企业用户来&#xff0c;最害怕的莫过于服务器遭受攻击了&#xff0c;比如被大量登录、网页被篡改、数据库被非法登录、网络日志异常等&#xff0c;尽管运维人员在此之前做好了全面的防范工作&#xff0c;但攻击再所难免&#xff0c;如何在服务器遭受攻击后能够迅速有效的处…

服务器被DDoS攻击,怎么破?

文章目录 前言网站受到DDoS的症状判断是否被攻击查看网络带宽占用查看网络连接TCP连接攻击SYN洪水攻击 防御措施TCP/IP内核参数优化iptables 防火墙预防防止同步包洪水&#xff08;Sync Flood&#xff09;Ping洪水攻击&#xff08;Ping of Death&#xff09;控制单个IP的最大并…

黑客攻击入侵服务器的6种常见方式

服务器被入侵既有一定的偶然性&#xff0c;也有一定的必然性&#xff0c;网络上有两种黑客&#xff0c;一种是漫无目的的漫天撒网形式的黑客&#xff0c;一种是目标明确只入侵指定目标的黑客&#xff0c;我们称前一种黑客叫菜鸟黑客&#xff0c;称后一种黑客叫高级黑客。简单的…

web服务器攻击的八种方式

随着互联网的高速发展&#xff0c;网络走进了千家万户&#xff0c;同时也有很大一部分人架设起了自己的网站。继而不安分的黑客们&#xff0c;又将目光对准了服务器攻击这个方式&#xff0c;从而破坏或取得服务器的管理权限。本文将主要讲述针对web服务器攻击的八种方式。 1、…

Webpack--模块热替换(HMR)

一、概述 &#xff08;1&#xff09;live reload 只要检测到代码改动就会自动重新构建&#xff0c;然后触发网页刷新 &#xff08;2&#xff09;webapack中的模块热替换 可以让代码在页面不刷新的前提下得到最新的改动&#xff0c;甚至不需要重新发起请求就能看到更新后的效…

HMR API及其原理

很久之前&#xff0c;遇到一个面试题&#xff1a;【在代码变更之后&#xff0c;如何实时看到更新后的页面效果呢&#xff1f;】 在传统的方案中&#xff0c;我们可以通过 live reload 也就是自动刷新页面的方式来解决的&#xff0c;不过随着前端工程的日益庞大&#xff0c;开发…

webpack实践之路(七):模块热替换HMR

HMR 模块热替换(Hot Module Replacement 或 HMR)允许在运行时更新各种模块&#xff0c;而无需进行完全刷新。 HMR主要是通过以下几种方式&#xff0c;来显著加快开发速度&#xff1a; 保留在完全重新加载页面时丢失的应用程序状态。只更新变更内容&#xff0c;以节省宝贵的开…