当前位置: 首页 > news >正文

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析

ThreadPoolExecutor 状态

ThreadPoolExecutor 继承了 AbstractExecutorService,并实现了 ExecutorService 接口,用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态,后 29 位表示 Worker 数量。

public class ThreadPoolExecutor extends `AbstractExecutorService` {private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));private static final int COUNT_BITS = Integer.SIZE - 3;private static final int CAPACITY   = (1 << COUNT_BITS) - 1;private static final int RUNNING    = -1 << COUNT_BITS;private static final int SHUTDOWN   =  0 << COUNT_BITS;private static final int STOP       =  1 << COUNT_BITS;private static final int TIDYING    =  2 << COUNT_BITS;private static final int TERMINATED =  3 << COUNT_BITS;private static int runStateOf(int c)     { return c & ~CAPACITY; }private static int workerCountOf(int c)  { return c & CAPACITY; }private static int ctlOf(int rs, int wc) { return rs | wc; }private static boolean runStateLessThan(int c, int s) {return c < s;}private static boolean runStateAtLeast(int c, int s) {return c >= s;}private static boolean isRunning(int c) {return c < SHUTDOWN;}......}

由源码中的常量定义可知,ThreadPoolExecutor 有 5 种线程池状态:

  • RUNNING:线程池接收新任务,会执行任务阻塞队列中的任务,ctl 前三位为 111
  • SHUTDOWN:线程池拒绝接收新任务,会执行任务阻塞队列中的任务,ctl 前三位为 000
  • STOP:线程池拒绝接收新任务,不会执行任务阻塞队列中的任务,尝试中断正在执行的任务,ctl 前三位为 001
  • TIDYING:所有任务被关闭,Worker 数量为 0,ctl 前三位为 010
  • TERMINATED:方法 terminated() 执行完毕,ctl 前三位为 011

通过 ctl 的结构可知,其前三位取值对应的线程池状态满足以下关系:

RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED

因此 runStateLessThan()runStateAtLeast()isRunning() 方法可以很方便的对线程池状态进行判断,不需要考虑当前 Worker 的具体数量。

img

执行任务源码分析

执行任务的方法为 execute(),其源码如下:

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();// 1.如果正在运行的线程数少于核心线程数,则尝试以给定的操作作为其第一个任务以启动一个新线程。addWorker()的调用以原子方式检查了 runState 和 workerCount,从而防止由于误报 false 而导致的线程增加if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// 2.如果一个任务成功加入了阻塞队列,那么我们仍然需要仔细检查是否应该添加一个线程(因为自上次检查以来原有的线程可能已死亡)或自进入此方法以来,线程池已关闭。所以如果重新检查状态已停止,有必要则直接回滚入队操作,或者如果没有正在运行的线程,则启动一个新线程。if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (!isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}// 3.如果无法将任务加入阻塞队列,那么我们尝试添加一个新的线程。 如果失败了,那我们就知道任务线程池已经关闭或饱和,因此拒绝任务。else if (!addWorker(command, false))reject(command);
}

execute() 方法根据 Worker 的数量和线程池状态来决定是新建 Worker 来执行任务,还是将任务添加到任务阻塞队列中。任务无法成功添加到阻塞队列或者新建 Worker 来执行任务失败,则执行任务拒绝策略。

private boolean addWorker(Runnable firstTask, boolean core) {retry:for (int c = ctl.get();;) {// 线程池不为 RUNNING 状态已停止工作,或为 SHUTDOWN 同时阻塞队列为空,则 Worker 创建失败if (runStateAtLeast(c, SHUTDOWN)&& (runStateAtLeast(c, STOP)|| firstTask != null|| workQueue.isEmpty()))return false;for (;;) {// 当前 Worker 数量大于 size,则拒绝创建 Worker。其中 core 为 true 对比核心线程数,core 为 false 则比对最大线程数。if (workerCountOf(c)>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))return false;// 以 CAS 方式将 Worker 数量加1,成功则跳出 retry 循环if (compareAndIncrementWorkerCount(c))break retry;// CAS 追加 Worker 数量失败,重新获取 ctl。c = ctl.get();  // 线程池状态改变,则需要基于新的线程池状态,重新执行外层循环来判断是否允许创建 Worker// 线程池状态不变,则继续执行内层循环,再次尝试以 CAS 方式增加 Worker 数量if (runStateAtLeast(c, SHUTDOWN))continue retry;}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {w = new Worker(firstTask);final Thread t = w.thread;if (t != null) {// 存储 Worker 的 HashSet 需要获取全局锁来保证添加 Worker 时的线程安全final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 在获取全局锁之后,再次获取线程池最新的状态int c = ctl.get();// 线程池状态为 RUNNING 或 SHUTDOWN 同时创建 Worker 的初始任务为 nullif (isRunning(c) ||(runStateLessThan(c, STOP) && firstTask == null)) {// 避免 Worker 的线程被重复启动(处于活动状态的线程无法再次启动)if (t.isAlive())throw new IllegalThreadStateException();workers.add(w);// 记录线程池存在过的最大 Worker 数int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;
}

addWorker() 方法中只有以下两种情况下可以创建 Worker:

  • 线程池状态为 RUNNIGNG
  • 线程池状态为 SHUTDOWN,且任务阻塞队列不为空,可以创建初始任务为 null 的 Worker

当 Worker 创建成功后,其线程就会启动,如果 Worker 创建失败或者线程启动失败,则会调用回滚方法 addWorkerFailed(),源码实现如下:

private void addWorkerFailed(Worker w) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {if (w != null)workers.remove(w);decrementWorkerCount();tryTerminate();} finally {mainLock.unlock();}
}

在这个过程中所添加的 Worker 实体,实现了 Runnable 接口,因此它实际上就是一个任务,其线程执行的任务就是它本身。所以在 addWorker() 方法中启动线程时,会调用 Worker 的 run() 方法,实际上内部就是调用了 runWorker() 方法,源码实现如下:

final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;// 释放了锁,以支持外部中断任务w.unlock();// 是否属于被中断的状态标识boolean completedAbruptly = true;try {// 获取 Worker 当前任务,或者从阻塞队列中取出下一个任务while (task != null || (task = getTask()) != null) {w.lock();// 当线程池为 STOPPING 状态时,要确保当前线程是被中断的(为了确保 shutdownNow() 方法在中断任务时能正确处理)if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!wt.isInterrupted())wt.interrupt();try {beforeExecute(wt, task);try {task.run();afterExecute(task, null);} catch (Throwable ex) {afterExecute(task, ex);throw ex;}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {processWorkerExit(w, completedAbruptly);}
}

从循环的条件可以看出,Worker 启动后,会首先执行自己的初始任务,然后再去任务阻塞队列中获取任务。任务流程执行结束或被中断后,则会调用 processWorkerExit() 方法。当 Worker 任务执行异常,或获取的任务最终为 null 时,该方法会将当前 Worker 从集合中删除,并尝试终止线程池。processWorkerExit() 方法的源码实现如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {// 异常中断的任务,Worker 数量没有及时调整,需要修正if (completedAbruptly)decrementWorkerCount();final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {completedTaskCount += w.completedTasks;workers.remove(w);} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();int c = ctl.get();if (runStateLessThan(c, STOP)) {// 如果线程池仍在可运行状态,由异常中断的导致被移除的 Worker 需要新增一个 null 任务来替换,以保持核心线程池数目或者阻塞队列的状态一致if (!completedAbruptly) {          int min = allowCoreThreadTimeOut ? 0 : corePoolSize;if (min == 0 && ! workQueue.isEmpty())min = 1;if (workerCountOf(c) >= min)return; // replacement not needed}addWorker(null, false);}
}

总结下来,ThreadPoolExecutor 执行任务主要由以下几步:

  1. 判断当前线程池状态是否允许创建 Worker

    • 线程池状态为 RUNNIGNG,可以创建 Worker
    • 线程池状态为 SHUTDOWN,且任务阻塞队列不为空,可以创建初始任务为 null 的 Worker
  2. 按照以下规则判断如何添加 Worker(执行任务):

    • 如果 Worker 数量小于核心线程数,则创建 Worker 来执行任务
    • 如果 Worker 数量大于等于核心线程数,则将任务添加到任务阻塞队列
    • 如果任务阻塞队列已满,则创建 Worker 来执行任务
    • 如果 Worker 数量已经达到最大线程数,此时执行任务拒绝策略
  3. Worker启动自身持有的线程,并执行自身实现的任务。启动后先执行自己的初始任务,然后再取任务阻塞队列中的任务。

关闭线程池源码分析

关闭 ThreadPoolExecutor 的方法主要有 shutdown()shutdownNow()

shutdown()

停止接收新任务,会把阻塞队列中的任务执行完毕。

public void shutdown() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 检查相关的线程修改权限checkShutdownAccess();// 通过CAS将线程池状态修改为SHUTDOWNadvanceRunState(SHUTDOWN);// 中断空闲的WorkerinterruptIdleWorkers();// 为 ScheduledThreadPoolExecutor 提供的方法入口,根据指定的终止策略,中断和清除队列中所有任务onShutdown();} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();
}

空闲 Worker 的中断涉及到锁的获取,具体处理过程可以参考以下的源码:

private void interruptIdleWorkers(boolean onlyOne) {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;// 中断线程前,尝试获取Worker的锁if (!t.isInterrupted() && w.tryLock()) {try {t.interrupt();} catch (SecurityException ignore) {} finally {w.unlock();}}if (onlyOne)break;}} finally {mainLock.unlock();}
}private void interruptIdleWorkers() {interruptIdleWorkers(false);
}

这里需要注意的是,Worker 除了实现了 Runnable 接口外,还继承了 AbstractQueuedSynchronizer,因此 Worker 本身还是一把锁,故有其自身实现锁的相关方法。以下是相关方法的实现:

public void lock()        { acquire(1); }
public boolean tryLock()  { return tryAcquire(1); }
public void unlock()      { release(1); }protected boolean tryAcquire(int unused) {if (compareAndSetState(0, 1)) {setExclusiveOwnerThread(Thread.currentThread());return true;}return false;
}
protected boolean tryRelease(int unused) {setExclusiveOwnerThread(null);setState(0);return true;
}

从源码中可以看出,Worker 的 lock() 方法调用了 AbstractQueuedSynchronizer 抽象类的 acquire() 方法,该方法内部又调用了 Worker 实现的 tryAcquire() 方法。tryAcquire() 方法通过 CAS 将锁的状态 STATE 从 0 设置为 1,由此可知 Worker 是一把不可重入锁。

正由于 Worker 是一把不可重入锁,正在执行任务的 Worker 是无法获取到锁的,只有线程池中没有执行任务的 Worker 才能被获取到锁,所以关闭线程池时对空闲 Worker 执行中断指令,实际上就是中断没有执行任务的空闲 Worker。正在执行任务的 Worker 在 shutdown() 方法被调用时不会被中断,而是继续执行完当前任务,随后再从任务阻塞队列中获取任务来执行,直到任务阻塞队列为空,紧接着当前 Worker 也会被删除。等到线程池中所有的 Worker 都被删除,以及任务阻塞队列任务清空后,线程池才会被终止掉。

总结下来,shutdown() 方法实现的内容就是:将线程池的状态设置为 SHUTDOWN,拒绝接收新的任务,等到线程池 Worker 数为 0,且任务阻塞队列为空时,关闭线程池。

shutdownNow()

停止接收新任务,中断当前所有的任务,并将线程池的状态置为 STOP。

public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();// 通过CAS方式将线程池状态设置为STOPadvanceRunState(STOP);// 中断所有WorkerinterruptWorkers();// 将任务阻塞队列中所有任务获取出来并返回tasks = drainQueue();} finally {mainLock.unlock();}// 尝试终止线程池tryTerminate();return tasks;
}private void interruptWorkers() {// 需要确保当前线程获取到了全局锁 this.mainLockfor (Worker w : workers)w.interruptIfStarted();
}

shutdownNow() 方法跟 shutdown() 方法的过程基本一致,在一些细节的具体实现上有所区别。首先会将线程池状态设置为 STOP,然后调用 interruptWorkers() 方法中断线程池中的所有 Worker,接着调用 tryTerminate() 方法尝试终止线程池,最后将任务阻塞队列中未被执行的任务返回。

shutdownNow() 方法调用后,线程池中的所有 Worker 都会被中断,包括正在执行任务的 Worker。也就是说 shutdownNow() 方法不会保证正在执行的任务会被安全的执行完毕,同时还会放弃任务阻塞队列中的所有任务。

tryTerminate()

前面多次提及的 tryTerminate() 方法可以确保线程池被正确的关闭,这里可以看看源码的具体实现:

final void tryTerminate() {for (;;) {int c = ctl.get();// 满足以下三种状态的线程池,不能被终止if (isRunning(c) ||runStateAtLeast(c, TIDYING) ||(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))return;// Worker数量不为0时,中断一个空闲的Worker;内部传播了shudown信号if (workerCountOf(c) != 0) {interruptIdleWorkers(ONLY_ONE);return;}final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 将线程池状态设置为TIDYINGif (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {try {// 终止线程池terminated();} finally {// 将线程池状态设置为TERMINATEDctl.set(ctlOf(TERMINATED, 0));termination.signalAll();}return;}} finally {mainLock.unlock();}// else retry on failed CAS}
}

线程池不能被终止的三种状态:

  • RUNNING - 正在执行任务
  • 大于或等于TIDYING - 正在进行终止流程或已经被终止
  • SHUTDOWN,但任务阻塞队列不为空 - 正在等待任务阻塞队列的任务被执行完

如果线程池不属于以上三种状态,此时可以通过中断一个空闲的 Worker,被中断的空闲 Worker 会在 getTask() 方法中返回 null,从而执行 processWorkerExit() 方法。在 processWorkerExit() 方法中,会删除当前的 Worker,又会再调用 tryTerminate() 方法,从而实现在所有空闲 Worker 之前传播 shutdown 信号。

以下是源码中提供的官方注释内容:

Transitions to TERMINATED state if either (SHUTDOWN and pool and queue empty) or (STOP and pool empty). If otherwise eligible to terminate but workerCount is nonzero, interrupts an idle worker to ensure that shutdown signals propagate. This method must be called following any action that might make termination possible – reducing worker count or removing tasks from the queue during shutdown. The method is non-private to allow access from ScheduledThreadPoolExecutor.

通过官方注释可知,只有在以下两种状态下,线程池能够被终止:

  • 线程池状态为 SHUTDOWN,Worker 数量为 0,任务阻塞队列为空
  • 线程池状态为 STOP,Worker 数量为 0

同时,在所有可能导致线程池终止的操作中都应该调用 tryTerminate() 方法来尝试终止线程池,因此线程池中 Worker 被删除时和任务阻塞队列中任务被删除时会调用 tryTerminate(),以达到在线程池符合终止条件时及时终止线程池。

相关文章:

【Java】线程池技术(三)ThreadPoolExecutor 状态与运行源码解析

ThreadPoolExecutor 状态 ThreadPoolExecutor 继承了 AbstractExecutorService&#xff0c;并实现了 ExecutorService 接口&#xff0c;用于管理线程。内部使用了原子整型 AtomicInteger ctl 来表示线程池状态和 Worker 数量。前 3 位表示线程池状态&#xff0c;后 29 位表示 …...

vscode使用内置插件断点调试vue2项目

1、首先项目中要开启source-map 在vue.config.js 文件中 module.exports {configureWebpack: {devtool: process.env.NODE_ENV ! "production" ? "source-map" : ,} }2、项目根目录新建.vscode/launch.js文件 {"configurations": [{"ty…...

centos7 低版本docker 升级为高版本

删除 docker yum -y remove docker*安装 yum 管理工具 yum install -y yum-utils添加国内镜像 manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo查看可用软件版本yum list docker-ce --showduplicates|sort -r安装 yum -y install docke…...

了解SD-WAN与传统WAN的区别

近年来&#xff0c;许多企业选择了SD-WAN作为他们的网络解决方案。云基础架构的SD-WAN不仅具备成本效益&#xff0c;而且提供更安全、更可靠的WAN连接&#xff0c;有助于实现持续盈利。客户能够更好地控制他们的网络&#xff0c;个性化定制且无需额外成本。 那么&#xff0c;为…...

技术干货 | AI驱动工程仿真和设计创新

在当今快速发展的技术领域&#xff0c;人工智能&#xff08;AI&#xff09;、机器学习和深度学习等技术已经成为推动工程仿真和设计创新的关键力量。Altair技术经理张晨在Altair “AI FOR ENGINEERS”线下研讨会上发表了相关精彩演讲&#xff0c;本文摘自演讲内容&#xff0c;与…...

深度分析SQL与NoSQL数据库:优缺点、使用场景及选型指南

在大数据和云计算时代&#xff0c;数据库技术的发展日新月异。SQL&#xff08;关系型数据库&#xff09;和NoSQL&#xff08;非关系型数据库&#xff09;作为两大主流数据库技术&#xff0c;各有其独特的优势和使用场景。本文将深入分析SQL和NoSQL的定义、优缺点、使用场景&…...

Linux基础 - shell基础

目录 零. 简介 一、常见的 Shell 类型 二、Shell 命令格式 三、基本命令 四、通配符 五、重定向 六、管道 七、变量 八、条件判断和流程控制 零. 简介 Shell 是一种命令解释器&#xff0c;在 Ubuntu 系统中&#xff0c;它负责接收用户在命令行中输入的命令&#xff0c…...

一文搞懂Linux命令行下载OneDrive分享文件

一文搞懂Linux命令行下载OneDrive分享文件 什么问题&#xff1f; 因为OneDrive有些坑&#xff0c;无法从分享界面获取真实下载链接&#xff0c;比如下面这个链接&#xff1a; https://connecthkuhk-my.sharepoint.com/:f:/g/personal/jhyang13_connect_hku_hk/EsEgHtGOWbJIm…...

SpringBoot 实现RequestBodyAdvice封装统一接受类功能

一、相关往期文章 SpringBootVue实现AOP系统日志功能_aop的vue完整项目 Spring AOP (面向切面编程&#xff09;原理与代理模式—实例演示_面向切面aop原理详解 二、需求分析 按照一般情况&#xff0c;统一接受类可以像以下的方式进行处理&#xff1a; 如果不想使用 Request…...

贪吃蛇——c语言版

文章目录 演示效果实现的基本功能技术要点源代码实现功能GameStart打印欢迎界面和功能介绍绘制地图创建蛇创建食物 GameRun打印提示信息蛇每走一步 GameEnd蛇死亡后继续游戏 演示效果 贪吃蛇1.0演示视频 将终端应用程序改为控制台主机 实现的基本功能 贪吃蛇地图绘制蛇吃食物的…...

ctr/cvr预估之WideDeep模型

ctr/cvr预估之Wide&Deep模型 在探索点击率&#xff08;CTR&#xff09;和转化率&#xff08;CVR&#xff09;预估的领域中&#xff0c;我们始终追求的是一种既能捕获数据中的线性关系&#xff0c;又能发现复杂模式的模型。因子分解机&#xff08;Factorization Machines, …...

快速生成基于vue-element的后台管理框架,实现短时间二次开发

你是否遇到过当你想要独立开发一个项目时对反复造轮子的烦扰&#xff1f; 这种流水线的操作实在让人受不了 而vue-element-template很好的帮你解决了这个烦恼 只需克隆下来&#xff0c;改改图标&#xff0c;模块名&#xff0c;甚至样式&#xff0c;就会变成一个全新的自己的项目…...

PCIe 7.0 要来了,一文看懂PCIe发展和技术

PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;&#xff0c;即外围组件高速串行扩展总线标准&#xff0c;自其诞生以来&#xff0c;已成为计算机硬件中不可或缺的一部分。它以其高速串行通信能力和不断演进的技术规范&#xff0c;满足了日益增长的数据…...

API-事件类型

学习目标&#xff1a; 掌握事件类型 学习内容&#xff1a; 事件类型鼠标事件焦点事件键盘事件文本事件focus选择器案例 事件类型&#xff1a; 鼠标事件&#xff1a; <title>事件类型-鼠标事件</title><style>div {width: 200px;height: 200px;background-c…...

解决poweroff时需要等待其他服务关闭问题

当我们在执行poweroff或者reboot时会出现某个服务需要等待才能关闭系统,这个时候就可以在服务中添加如下: After=shutdown.target Conflicts=reboot.target halt.target poweroff.target Before=shutdown.target reboot.target halt.target poweroff.target具体实例: [Uni…...

ThinkPHP-导入Excel表格(通用版)

一、版本说明 1.PHP8.2、MySQL8.0、ThinkPHP8.0 2.使用前安装phpspreadsheet composer require phpoffice/phpspreadsheet 二、技术说明 因本人采用前后端分离&#xff0c;因此上传文件以及导入表格为分离开发&#xff0c;如无需分离开发则自行合并开发即可。 1.第一步&a…...

毕昇jdk教程

毕昇jdk教程指南链接&#xff1a;Wiki - Gitee.com...

【R语言】地理探测器模拟及分析(Geographical detector)

地理探测器模拟及分析 1. 写在前面2. R语言实现2.1 数据导入2.2 确定数据离散化的最优方法与最优分类2.3 分异及因子探测器&#xff08;factor detector&#xff09;2.4 生态探测器&#xff08;ecological detector&#xff09;2.5 交互因子探测器&#xff08;interaction dete…...

深入理解Qt属性系统[Q_PROPERTY]

Qt 属性系统是 Qt 框架中一个非常核心和强大的部分&#xff0c;它提供了一种标准化的方法来访问对象的属性。这一系统不仅使得开发者能够以一致的方式处理各种数据类型&#xff0c;还为动态属性的管理提供了支持&#xff0c;并与 Qt 的元对象系统紧密集成。在这篇文章中&#x…...

【C语言课程设计】员工信息管理系统

员工信息管理系统 在日常的企业管理中&#xff0c;员工信息的管理显得尤为重要。为了提高员工信息管理的效率&#xff0c;我们设计并实现了一个简单的员工信息管理系统。该系统主要使用C语言编写&#xff0c;具备输入、显示、查询、更新&#xff08;增加、删除、修改&#xff…...

「动态规划」如何求最长递增子序列的长度?

300. 最长递增子序列https://leetcode.cn/problems/longest-increasing-subsequence/description/ 给你一个整数数组nums&#xff0c;找到其中最长严格递增子序列的长度。子序列是由数组派生而来的序列&#xff0c;删除&#xff08;或不删除&#xff09;数组中的元素而不改变其…...

深度神经网络DNN概念科普

深度神经网络DNN概念科普 深度神经网络&#xff08;Deep Neural Network, DNN&#xff09;是机器学习领域中一类具有多层结构的神经网络模型&#xff0c;它能够通过学习数据中的复杂模式来解决非线性问题。下面是对深度神经网络的详细解析&#xff1a; 基本组成部分 输入层&…...

Tomcat WEB站点部署

目录 1、使用war包部署web站点 2、自定义默认网站目录 3、部署开源站点&#xff08;jspgou商城&#xff09; 对主机192.168.226.22操作 对主机192.168.226.20操作 上线的代码有两种方式&#xff1a; 第一种方式是直接将程序目录放在webapps目录下面&#xff0c;这种方式…...

IPv6 中 MAC 33:33 的由来

一、33:33 由来 1. RFC9542 - 2024-05-02 Note IANA allocates addresses under the IANA OUI (00-00-5E) as explained in [RFC9542]. Unicast addresses under the IANA OUI start with 00-00-5E, while multicast addresses under the IANA OUI start with 01-00-5E. In t…...

告别手动邮件处理:使用imbox库轻松管理你的收件箱

imbox库简介&#xff1a; imbox是一个强大的Python库,专为与IMAP服务器交互而设计.IMAP&#xff08;Internet Message Access Protocol&#xff09;是一种用于电子邮件的标准协议,允许用户在远程服务器上管理邮件.imbox库通过IMAP协议与邮件服务器通信,帮助用户轻松地读取、搜索…...

Ubuntu 18.04 安装 PCL 1.14.1

在进行科研项目时&#xff0c;我们常常需要将 C 和 Python 结合起来编程。然而&#xff0c;每次将 PCL&#xff08;Point Cloud Library&#xff09;的内容添加到 CMakeLists.txt 文件中时都会报错。在深入分析后&#xff0c;我们推测可能是当前使用的 PCL 1.8 版本与现有程序不…...

公司logo设计大全怎么找?直接帮你设计logo

公司logo设计大全怎么找&#xff1f;在品牌塑造的过程中&#xff0c;Logo无疑是至关重要的一环。一个优秀的Logo不仅能够有效传达公司的核心理念和品牌形象&#xff0c;还能在消费者心中留下深刻的印象。然而&#xff0c;对于许多初创公司或小型企业来说&#xff0c;制作出适合…...

如何调整C#中数组的大小

前言 数组存储多个相同类型的一种非常常用的数据结构。它长度是固定&#xff0c;也就是数组一旦创建大小就固定了。C# 数组不支持动态长度。那在C#中是否有方法可以调整数组大小呢&#xff1f;本文将通过示例介绍一种调整一维数组大小的方法。 方法 数组实例是从 System.Arr…...

通过言语和非言语检索线索描绘睡眠中的记忆再激活茗创科技茗创科技

摘要 睡眠通过重新激活新形成的记忆痕迹来巩固记忆。研究睡眠中记忆再激活的一种方法是让睡眠中的大脑再次暴露于听觉检索线索(定向记忆再激活范式)。然而&#xff0c;记忆线索的声学特性在多大程度上影响定向记忆再激活的有效性&#xff0c;目前还没有得到充分探索。本研究通…...

MDPI旗下SSCI最新影响因子目录出炉!“水刊“Sustainability表现如何?

本周投稿推荐 SSCI • 1区&#xff0c;4.0-5.0&#xff08;无需返修&#xff0c;提交可录&#xff09; EI • 各领域沾边均可&#xff08;2天录用&#xff09; CNKI • 7天录用-检索&#xff08;急录友好&#xff09; SCI&EI • 4区生物医学类&#xff0c;0.1-0.5&…...