ThreadPoolExecutor工作原理及源码详解

一、前言

创建一个线程可以通过继承Thread类或实现Runnable接口来实现,这两种方式创建的线程在运行结束后会被虚拟机回收并销毁。若线程数量过多,频繁的创建和销毁线程会浪费资源,降低效率。而线程池的引入就很好解决了上述问题,线程池可以更好的创建、维护、管理线程的生命周期,做到复用,提高资源的使用效率。也避免了开发人员滥用new关键字创建线程的不规范行为。

说明:阿里开发手册中明确指出,在实际生产中,线程资源必须通过线程池提供,不允许在应用中显式的创建线程。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

通过分析ThreadPoolExecutor线程池的工作原理、核心参数以及执行过程来深入了解线程池相关工作原理。

二、ThreadPoolExecutor

ThreadPoolExecutor是java中实现线程池的核心类,主要用于管理和异步执行任务。从以下几个方面分析工作原理

1、生命周期

线程存在生命周期,同样线程池也有生命周期。生命周期中存在5个状态。

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;

线程池的开启到关闭过程就是这5个状态流转过程,状态之间转换流转如下图。

5个状态描述说明如下

状态

含义

RUNNING

运行状态,该状态下线程池可以接受新的任务,也可以处理阻塞队列中的任务

执行 shutdown 方法可进入 SHUTDOWN 状态

执行 shutdownNow 方法可进入 STOP 状态

SHUTDOWN

待关闭状态,不再接受新的任务,继续处理阻塞队列中的任务

当阻塞队列中的任务为空,并且工作线程数为0时,进入 TIDYING 状态

STOP

停止状态,不接收新任务,也不处理阻塞队列中的任务,并且会尝试结束执行中的任务

当工作线程数为0时,进入 TIDYING 状态

TIDYING

整理状态,此时任务都已经执行完毕,并且也没有工作线程

执行 terminated 方法后进入 TERMINATED 状态

TERMINATED

终止状态,此时线程池完全终止了,并完成了所有资源的释放

线程池的重点之一就是控制线程资源合理高效的使用,所以必须控制工作线程的个数,所以需要保存当前线程池中工作线程的个数。ThreadPoolExecutor中使用一个AtomicInteger类型的ctl属性存储了线程池的状态和工作线程个数。

ctl的高3位用来表示线程池的状态(runState),低29位用来表示工作线程的个数(workerCnt)。因为线程池有5个状态,2位只能表示4个状态,所以用3位来表示5个状态。

2、创建线程池

1. 构造方法

通过ThreadPoolExecutor类的构造方法,来创建一个线程池,其中构造方法上有7大核心参数,通过7大核心参数的配置来定制化线程池。

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;}

7大核心参数含义:

corePoolSize:核心线程数

maximumPoolSize:最大线程数

keepAliveTime:非核心线程空闲时间

unit:keepAliveTime的时间单位

workQueue:工作阻塞队列

ThreadFactory:线程工厂提供创建线程的方式,默认使用Executors.defaultThreadFactory()创建。

handler:拒绝策略

2. 核心参数详解

ThreadPoolExecutor的构造方法中包含了7大核心参数,通过配置核心参数可以定制化线程池。接下来分别介绍参数

corePoolSize 和 maximumPoolSize

通过corePoolSize和maximumPoolSize在构造方法中设置线程池边界,来调整线程池中的工作线程数量。工作线程个数自动调整有两个场景:

a. 工作线程个数小于corePoolSize

当线程池提交一个新任务,且工作线程个数小于corePoolSize时,即使其他工作线程空闲,也会创建一个新线程来执行任务

b. 线程数量介于corePoolSize和maximumPoolSize之间

当运行的工作线程数量大于corePoolSize但小于maximumPoolSize,且阻塞队列已满时 才会创建非核心工作线程。

默认情况下,即使是核心线程也是在新任务到达时开始创建和启动的。若使用非空队列创建线程池,可通过重写prestartCoreThread或prestartAllCoreThreads方法动态覆盖,进行线程预启动。

keepAliveTime

keepAliveTime参数用来设置工作线程空闲时间。非核心线程通过poll方法获取任务,若获取不到,那么线程会keepAliveTime长的时间进行重复获取。当执行时间大于keepAliveTime时,线程就会采用CAS(比较-替换)随机方式进行线程销毁流程。

workQueue

workQueue参数用来指定存放提交任务的队列,任务BlockingQueue都可以用来传输和保存提交的任务。队列大小与线程数量之间存在关系:

a. 若线程数小于corePoolSize,对于提交的新任务会创建一个新的线程处理,不会把任务放入到队列。

b. 若线程数介于corePoolSize和maximumPoolSize之间,新提交的任务会被放到阻塞队列。

c. 若线程池处于饱和状态,即无法创建新线程且阻塞队列已满,那么新提交的任务交给拒绝策略处理。

线程池常见的阻塞队列一般有SynchronousQueue(同步队列)、LinkedBlockingQueue(无界阻塞队列)、ArrayLiBlockQueue(有界阻塞对列)、DelayedWorkQueue(延迟阻塞队列)。

  • SynchronousQueue不是真正意义上的队列,它没有容量也不存储任务,只是维护一组线程,等待着任务加入和移除队列,相当于直接交接任务给具体制定的队列,在CacheThreadPool线程池中使用。
  • LinkedBlockingQueue是采用链表实现的无界队列。若不预定义LinkedBlockingQueue队列容量,当所有核心线程都在执行时,就会无限添加任务到队列中,可能导致OOM,且这种场景下maximumPoolSize的值对线程数无影响。在SingleThreadExecutor和FixedThreadPool线程池中使用。
  • ArrayBlockingQueue是通过数组实现的有界队列。有界队列和有限的maximumPoolSize一起使用有助于防止资源耗尽。使用ArrayBlockingQueue可以根据应用场景,可以预先估计线程数和队列容量,互相权衡队列大小和线程数:
    1. 大队列和小线程数:减少线程数,可以最大限度的减少CPU使用率、操作系统资源和上下文切换开销,可能会导致吞吐量降低
    2. 小队列和大线程数:较大的线程,若任务提交速度快,会在短时间提升CPU使用率,提高系统吞吐量。若任务经常阻塞(如IO阻塞),会使得CPU切换更加频繁,可能会有更大的调度开销,这也会降低吞吐量
  • DelayedWorkQueue是采用数组的完全二叉树实现小根堆特性的延迟队列。在堆中每个父节点的值都不大于子节点的值,因此堆顶元素(数组第一个元素)总是当前队列中延时最小的任务。队列中元素是ScheduledFutureTask对象,对象封装了任务的执行逻辑以及延迟time信息。在ScheduledThreadPool线程池中使用。

threadFactory

该参数提供了线程池中线程创建方式,这里使用了工厂模式ThreadFactory创建新线程,默认情况下使用Executors.defaultThreadFactory创建,它创建的线程都在同一个ThreadGroup,且具有想同的NORM_PRIORITY优先级非守护进程状态。也可以自定义ThreadFactory修改线程的名称、线程组、优先级以及守护程序状态等。

handler

若线程池处于饱和状态没有足够的线程数或队列空间来处理提交的任务,或者线程池已处于关闭状态但还在处理进行中的任务,那么新提交的任务就会由拒绝策略处理。

出现以上任何情况,execute方法都会调用RejectExecutionHandler.rejectExecution()方法进行拒绝策略处理。线程池提供了四种预定义的拒绝处理策略

ThreadPoolExecutor.AbortPolicy(默认策略):

        行为:当工作队列已满且无法再添加新任务时,直接抛出RejectExecutionException异常

        场景:适用于哪些不能容忍任务被丢弃或延迟执行的场景。因为会立即通知调用者任务被拒绝,从而可以采取相应的处理措施。如订单处理

ThreadPoolExecutor.DisCardPolicy:

        行为:当工作队列已满且无法再添加新任务时,直接丢弃新任务,不做任务处理。

        场景:适用于哪些任务可以被安全忽略的场景,或任务执行与否对系统整体影响不大的情况。如日志收集

ThreadPoolExecutor.DiscardOldestPolicy:

        行为:当工作队列已满且无法再添加新任务时,丢弃队列中最早的任务(即等待时间最长的任务),然后尝试重新提交当前任务。

        场景:适用于哪些任务可以相互替代,或较早的任务执行结果对当前系统状态影响不大的场景。如消息队列处理系统,若消息队列已满且新消息已来,可以采用该策略

ThreadPoolExecutor.CallerRunsPolicy:

        行为:当工作队列已满且无法再添加新任务时,由提交任务的线程来执行该任务,即任务在调用线程(提交任务的线程)的上下文中执行。

        场景:适用于需保证任务不被丢弃,且任务执行时间相对较短的场景。如在线视频处理

除了上述四种提供的拒绝策略外,还可通过实现RejectedExecutionHandler接口来自定义拒绝策略。

3、工作流程

了解了线程池中生命周期和核心参数,接下来了解下线程池整体工作流程,如图:

上图是一张线程池工作的精简图,线程池工作流程主要包含提交任务、创建工作线程并启动、获取任务并执行、销毁工作线程几部分。

1. 提交任务

当线程池通过execute提交任务时,线程池有三种处理情况,分别是创建工作线程执行该任务、将任务添加到阻塞队列、拒绝该任务。提交任务过程可以拆分一下几步:

a. 线程数小于corePoolSize时,通过addWorker创建新的核心线程处理该任务

b. 线程数等于corePoolSize且非空闲时,将任务添加到阻塞队列中。

c. 若添加成功,需二次验证线程池状态,若为非RUNNING状态,则需将该任务从队列中移除,然后拒绝该任务。 若为RUNNING状态且当前工作线程数为0,则需主动创建一个空任务的非核心线程来执行队列中该任务。

d. 若添加失败,则队列已满,创建新的临时线程(非核心线程)执行该任务。

e. 若创建临时线程(非核心线程)失败,则说明工作线程等于maximumPoolSize,只能拒绝该任务。

ThreadPoolExecutor.execute源码解析

//ThreadPoolExecutor.execute()方法执行任务
public void execute(Runnable command){if(command == null){throw new NullPointerExecption();}int c = ctl.get();//获取ctl低29位中的工作线程数与核心线程数比较,若小于核心线程数 直接创建worker对象执行任务if(workerCount(c) < corePoolSize){if(addWorker(command, true))return;c = ctl.get();}//当线程池为RUNNING状态且worker数量超过核心线程数,任务直接放入到阻塞队列中if(isRunning(c) && workerQueue.offer(command)){int recheck = ctl.get();//线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()拒绝操作。//这儿为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。if(!isRunning(recheck) && remove(command)){拒绝处理任务reject(command);}else if(workerCountof(recheck) == 0){//当工作线程个数为0时,创建一个空任务的非核心工作线程执行队列中任务//这儿为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0addWorker(null, false);}            }// 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。// 这儿有3点需要注意:// 1. 线程池不是运行状态时,addWorker内部会判断线程池状态// 2. addWorker第2个参数表示是否创建核心线程// 3. addWorker返回false,则说明任务执行失败,需要执行reject拒绝操作else if(!addWorker(command, false))        reject(command);}
2. 创建工作线程并启动

ThreadPoolExecutor线程池核心任务单元是Worker内部类实现的。创建工作线程首先就是创建Worker对象,Worker对象是通过核心addWorker方法创建工作线程,在通过Worker对象中的Thread对象启动线程。addWorker方法包含两部分操作。

a. 校验线程池状态以及工作线程个数

        i. 先判断线程池状态

        ii. 在判断工作线程个数

        iii. 通过CAS原子性对ctl属性低29位的值+1

b. 添加工作线程并启动工作线程

        i. 所有校验通过后,先new Worker对象,再将worker对象放大HashSet集合中

        ii. 然后拿到worker对象中的Thread对象执行.start方法启动工作线程,通过runWorker执行任务

addWorker方法执行源码解析

private boolean addWorker(runnable firstTask, boolean core){//外层for循环是在校验线程池状态//内层for循环是在校验工作线程个数//retry是给外层for添加一个标记,是为了方便在内层for循环跳出外层for循环retry:for(; ;){//获取存储线程池状态的ctl属性int c = ctl.get();//拿到ctl的高3位的值 即线程池状态int rs = runStateOf(c);//========================线程池状态判断=======================////如果线程池状态是SHUTDOWN;并且此时阻塞队列中有任务 核心线程数为0,添加一个工作线程(非核心线程)去处理阻塞队列的任务//判断线程池状态是否大于等于SHUTDOWN,若满足可能为SHUTDOWN、STOP、TIDING、TERMINATED,说明线程池状态不是RUNNING(才能处理任务)if(rs >= SHUTDOWN &&//若三个条件都满足,就代表是要添加非核心线程去处理阻塞队列中的任务//若三个条件有一个不满足,返回false 配合!,表示不需要添加工作线程!(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty())){//不需要添加工作线程return false;}for(; ;){//========================工作线程个数判断=======================////获取ctl低29位的值,代表当前工作线程的个数int wc = workerCountOf(c);//若工作线程个数大于第29位最大值,不可以再添加工作线程,返回falseif(wc >= CAPACITY || //基于core来判断添加的是否为核心工作线程//若是核心线程:基于corePoolSize来判断//若是非核心线程:基于maximumPoolSize来判断wc >= (core ? corePoolSize : maximumPoolSize))//代表不能添加,工作线程个数不满足要求return false;//若可以添加,直接对ctl的第29位采用CAS方式 直接+1。因为添加工作线程可能出现并发情况 使用CAS保证操作的原子性if(compareAndIncrementWorkerCount(c))//CAS成功后,直接退出外层for循环,代表可以执行添加工作线程的操作了break retry;//若CAS操作失败,重新获取一次ctl的值c = ctl.get();//判断重新获取的ctl中,线程池的状态跟之前的是否有区别//若状态不一致,说明有变化,重新去判断线程池的状态if(runStateOf(c) != rs)//跳出一次外出for循环continue retry;}}//========================添加工作线程以及启动工作线程=======================////声明三个变量//工作线程启动了没 默认falseboolean workerStarted = false;//工作线程添加了没 默认falseboolean workerAdded = false;//工作线程 默认nullWorker w = null;try{//构建工作线程,并将任务传递进去w = new Worker(firstTask);//获取Worker中的Thread对象final Thread t = w.thread;//判断Thread对象是否为null,在new Worker时 内部会通过给予的ThreadFactory去构建Thread对象交给Worker//一般若为null,代表ThreadFactory有问题if(t != null){//加锁,保证使用workers(HashSet集合 线程不安全)成员变量以及对largestPoolSize赋值时 保证线程安全final ReentrantLock mainLock = this.mainLock;//加锁原因是HashSet线程不安全 加锁main.lock();try{//再次获取线程池状态int rs = runStateOf(ctl.get());//再次判断线程池状态//若状态满足 rs < SHUTDOWN,说明线程池状态为RUNNING,状态正常可以添加工作线程 执行if代码块//若状态为SHUTDOWN,且firstTask任务为null,添加非核心工作线程处理阻塞队列中的任务if(rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)){//到这可以添加工作线程了//校验ThreadFactory创建线程后 线程是否已自动启动,若已启动,抛异常if(t.isAlive())throw new IllegalThreadStateExecption();//private final HashSet<Worker> workers = new HashSet<Worker>();//将new好的worker添加到HashSet中workers.add(w);//获取HashSet的size,拿到工作线程的个数int s = workers.size();//largestPoolSize-表示历史最大线程数记录//若当前最大工作线程个数 大于历史最大线程个数记录,就赋值if(s > largestPoolSize)largestPoolSize = s;//添加工作线程成功workerAdded = true;}}finally{mainLock.unLock();}//工作线程添加成功后 启动工作线程if(workerAdded){//直接启动Worker中的线程t.start();//启动工作线程成功workerStarted = true;}}}finally{//做补偿的操作,若工作线程启动失败,将这个添加失败的工作线程处理掉if(!workerStarted)addWorkerFailed(w);}//返回工作线程是否启动成功return workerStarted;
}
3. 获取任务并执行

通过执行runWorker方法来获取任务并执行,runWorker方法执行主要有以下几步

a. 获取当前线程以及worker中封装的任务

b. 判断当前任务是否为null,若不为null,直接执行。若为null,重阻塞队列中获取任务

c. 判断线程池状态以及当前线程中断标记位,若线程池状态为STOP或当前线程中断标记位为false,则中端当前线程

d. 执行任务

runWorker方法源码解析

final void runWorker(Worker w) {//拿到当前工作线程(w对应的线程)Thread wt = Thread.currentThread();//拿到Worker对象中封装的的任务Runnable task = w.firstTask;//将worker的firstTask归位w.firstTask = null;// 将Worker的state状态归为0,代表可以被中断w.unlock(); // allow interrupts// 任务执行时,勾子函数中是否出现异常的标识,默认为true-出现异常boolean completedAbruptly = true;try {//获取任务的第一个方式,就是执行execute、submit时,转入的任务直接处理//获取任务的第二个方式,从工作队列中获取任务执行。while (task != null || (task = getTask()) != null) {// 加锁,在SHUTDOWN状态下,当前线程不允许被中断// 并且Worker内部的锁,并不是可重入锁,因为中断时,也需要对worker进行lock,不能获取就代表当前线程正在执行任务w.lock();//如果线程池状态变为了STOP状态,必须将当前线程中断// 第一个判断:判断当前线程池状态是否为STOP// 第二个判断:查看线程中断标记位并归位,若为false 说明不是STOP,若变为true,需要再次查看是否是否是并发操作导致线程池为STOPif ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) &&// 查看当前线程中断标记是否为false,若为false,就执行wt.interrupt();!wt.isInterrupted())wt.interrupt();try {// 执行任务的勾子函数,前置增强(不是动态代理),可以重写这个方法beforeExecute(wt, task);Throwable thrown = null;try {// 执行任务task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {// 执行任务的勾子函数,后置增强(不是动态代理),可以重写这个方法afterExecute(task, thrown);}} finally {// 将task置为nulltask = null;// 将执行成功的任务个数+1 w.completedTasks++;// 将线程的state状态标记位设置为0,表示可以通过SHUTDOWN中断当前线程w.unlock();}}completedAbruptly = false;} finally {// 自旋操作被退出,说明线程池正在结束processWorkerExit(w, completedAbruptly);}
}

三、总结

Executor框架主要由三部分组成,任务任务的执行者执行结果,ThreadPoolExecutor和ScheduledThreadPoolExecutor的设计思想也是将这三个关键要素进行了解耦,将任务的提交和执行分离。线程池是一种基于池化技术的线程管理工具,能够降低资源消耗、提高响应速度、提高线程的可管理性。

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

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

相关文章

计算机组成原理---机器中的数字表示

二进制&#xff0c;八进制&#xff0c;十六进制之间转化 十进制转二进制 75.3的整数部分75&#xff1a; 75.3小数部分0.3&#xff1a; 原则&#xff1a;1.先除r/乘r得到的是结果部分中接近小数点的数字 2.都是取结果一部分&#xff08;余数/整数部分&#xff09;&#xff0c;使…

51单片机15(直流电机实验)

一、序言&#xff1a;我们知道在单片机当中&#xff0c;直流电机的控制也是非常多的&#xff0c;所以有必要了解一些这个电机相关的一些知识&#xff0c;以及如何使用单片机来控制这个电机&#xff0c;那么在没有学习PWM之前&#xff0c;我们先简单的使用GPIO这个管脚来控制电机…

npm提示 certificate has expired 证书已过期 已解决

在用npm新建项目时&#xff0c;突然发现报错提示 : certificate has expired 证书已过期 了解一下&#xff0c;在网络通信中&#xff0c;HTTPS 是一种通过 SSL/TLS 加密的安全 HTTP 通信协议。证书在 HTTPS 中扮演着至关重要的角色&#xff0c;用于验证服务器身份并加密数据传输…

vue实现电子签名、图片合成、及预览功能

业务功能&#xff1a;电子签名、图片合成、及预览功能 业务背景&#xff1a;需求说想要实现一个电子签名&#xff0c;然后需要提供一个预览的功能&#xff0c;可以查看签完名之后的完整效果。 需求探讨&#xff1a;后端大佬跟我说&#xff0c;文档我返回给你一个PDF的oss链接…

【书生大模型实战营(暑假场)】入门任务一 Linux+InternStudio 关卡

入门任务一 LinuxInternStudio 关卡 参考&#xff1a; 教程任务 1 闯关任务 1.1 基于 VScode 的 SSH 链接 感谢官方教程的清晰指引&#xff0c;基于VS code 实现 SSH 的链接并不困难&#xff0c;完成公钥配之后&#xff0c;可以实现快速一键链接&#xff0c;链接后效果如下…

XXE -靶机

XXE靶机 一.扫描端口 进入xxe靶机 1.1然后进入到kali里 使用namp 扫描一下靶机开放端口等信息 1.2扫描他的目录 二 利用获取的信息 进入到 robots.txt 按他给出的信息 去访问xss 是一个登陆界面 admin.php 也是一个登陆界面 我们访问xss登陆界面 随便输 打开burpsuite抓包 发…

【MySQL】事务 【下】{重点了解读-写 4个记录隐藏列字段 undo log日志 模拟MVCC Read View sel}

文章目录 1.MVCC数据库并发的场景重点了解 读-写4个记录隐藏列字段 2.理解事务undo log日志mysql日志简介 模拟MVCC 3.Read Viewselect lock in share modeMVCC流程RR与RC 1.MVCC MVCC&#xff08;Multi-Version Concurrency Control&#xff0c;多版本并发控制&#xff09;是…

20240801 每日AI必读资讯

&#x1f50a;OpenAI向ChatGPT Plus用户推出高级语音模式 - 只给一小部分Plus用户推送&#xff0c;全部Plus用户要等到秋季 - 被选中的Alpha 测试的用户将收到一封包含说明的电子邮件&#xff0c;并在其移动应用中收到一条消息。 - 同时视频和屏幕共享功能继续推出&#xff…

ElasticSearch父子索引实战

关于父子索引 ES底层是Lucene,由于Lucene实际上是不支持嵌套类型的,所有文档都是以扁平的结构存储在Lucene中,ES对父子文档的支持,实际上也是采取了一种投机取巧的方式实现的. 父子文档均以独立的文档存入,然后添加关联关系,且父子文档必须在同一分片,由于父子类型文档并没有…

echarts加载区域地图,并标注点

效果如下&#xff0c;加载了南海区域的地图&#xff0c;并标注几个气象站点&#xff1b; 1、下载区域地图的JSON&#xff1a;DataV.GeoAtlas地理小工具系列 新建nanhai.json&#xff0c;把下载的JSON数据放进来 说明&#xff1a;如果第二步不打勾&#xff0c;只显示省的名字&a…

ECCV 2024前沿科技速递:GLARE-基于生成潜在特征的码本检索点亮低光世界,低光环境也能拍出明亮大片!

在计算机视觉与图像处理领域&#xff0c;低光照条件下的图像增强一直是一个极具挑战性的难题。暗淡的光线不仅限制了图像的细节表现&#xff0c;还常常引入噪声和失真&#xff0c;极大地影响了图像的质量和可用性。然而&#xff0c;随着ECCV 2024&#xff08;欧洲计算机视觉会议…

应急靶场(11):【玄机】日志分析-apache日志分析

题目 提交当天访问次数最多的IP&#xff0c;即黑客IP黑客使用的浏览器指纹是什么&#xff0c;提交指纹的md5查看index.php页面被访问的次数&#xff0c;提交次数查看黑客IP访问了多少次&#xff0c;提交次数查看2023年8月03日8时这一个小时内有多少IP访问&#xff0c;提交次数 …

OrangePi AI Pro 固件升级 —— 让主频从 1.0 GHz 到 1.6 GHz 的巨大升级

前言 OrangePi AI Pro 最近发布了Ascend310B-firmware 固件包&#xff0c;据说升级之后可以将 CPU 主频从 1.0 GHz 提升至 1.6 GHz&#xff0c;据群主大大说&#xff0c;算力也从原本的 8T 提升到了 12T&#xff0c;这波开发板的成长让我非常的 Amazing 啊&#xff01;下面就来…

Linux命令行 复制模式/扩展模式 调用系统功能切换

问题背景 公司软件需要从window 适配国产操作系统&#xff0c;目前使用wine方案。在我们软件有个切换屏幕模式的功能&#xff0c;需要支持用户在我们软件内&#xff0c;切换复制模式/扩展模式。 在linux 下 uos/deepin 等系统。如果要从复制模式设置为扩展模式使用命令行时&a…

Windows下nmap命令及Zenmap工具的使用方法

一、Nmap简介 nmap是一个网络连接端扫描软件&#xff0c;用来扫描网上电脑开放的网络连接端。确定哪些服务运行在哪些连接端&#xff0c;并且推断计算机运行哪个操作系统&#xff08;这是亦称 fingerprinting&#xff09;。它是网络管理员必用的软件之一&#xff0c;以及用以评…

【Bug收割机】已解决使用maven插件打包成功,在控制台使用mvn命令打包失败问题详解,亲测有效!

文章目录 前言问题分析报错原因解决方法私域 前言 在maven项目中&#xff0c;大家经常会使用maven插件来打包项目文件 但是有的人也习惯使用mvn命令在控制台直接进行打包&#xff0c;因为这样可以自定义组装一些命令&#xff0c;使用起来也更加灵活方便&#xff0c;比如mvn pa…

C++进阶-哈希扩展(位图和布隆过滤器)

1. 位图 1.1 位图概念 面试题 给40亿个不重复的无符号整数&#xff0c;没排过序。给一个无符号整数&#xff0c;如何快速判断一个数是否在 这40亿个数中。【腾讯】 解题思路1&#xff1a;暴⼒遍历&#xff0c;时间复杂度O(N)&#xff0c;太慢 解题思路2&#xff1a;排序⼆分查…

mybatis-plus中出现Field ‘id‘ doesn‘t have a default value问题解决方法

问题分析&#xff1a; 出现这个原因&#xff0c;主要是因为mybatis-plus自身查询的特性&#xff0c;因为查询都是它自己内部设定好的参数&#xff0c;一般为了简便&#xff0c;都会默认自己底层的数据库对应的主键id字段是自增的&#xff0c;也就是mybatis-plus认为不需要id,每…

重磅惊喜!OpenAI突然上线GPT-4o超长输出模型!「Her」高级语音模式已开放测试

在最近的大模型战争中&#xff0c;OpenAI似乎很难维持霸主地位。虽然没有具体的数据统计&#xff0c;但Claude3.5出现后&#xff0c;只是看网友们的评论&#xff0c;就能感觉到OpenAI订阅用户的流失&#xff1a; Claude3.5比GPT-4o好用&#xff0c;为什么我们不去订阅Claude呢&…

学习c语言第18天(字符串和内存函数)

1.函数介绍 1.1 strlen size_t(就是无符号整形) strlen(const char * str); 字符串已经\0作为结束标志&#xff0c;strlen函数返回的是在字符串中\0前面出现的字符个数(不包 含\0) 参数指向的字符串必须要以\0结束。 注意函数的返回值为size_t&#xff0c;…