还不会线程池?JUC线程池源码级万字解析

线程池主要解决了两个问题:
第一个是当大量执行异步任务的时候提供较好的性能;在不使用线程池的时候,每次需要执行一个异步任务都需要新建一个 Thread 来进行,而线程的创建和销毁都是需要时间的,所以可以通过线程池来实现线程的复用,从而解决这个问题。
同时线程池也提供了一种资源限制和管理的手段,比如可以限制线程的个数、动态的增加线程等;ThreadPoolExecutor 保留了一些基本的统计数据,比如当前线程池完成的任务数目等。

1)介绍

1.1)使用案例

ThreadPoolExecutor 是 Java 中 java.util.concurrent包的一部分,用于管理线程池的实现。它提供了一种灵活的方法来 创建和管理线程池,以便有效地执行大量的任务。它允许开发人员配置线程池的各种参数,如核心线程数、最大线程数、线程闲置时间、任务队列等。简单来说,它就是一个内部维护了一个线程池的任务执行器(Executor)。

在正式开始阅读源码之前,先来看一下应该如何使用这个它来执行任

public class ThreadPoolExecutorSourceRead {public static void main(String[] args) {// 构造线程池执行器ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,4,10,TimeUnit.SECONDS,new LinkedBlockingQueue<>(100));for (int i = 10; i >= 0; i--) {threadPoolExecutor.execute(() -> {try {Thread.sleep(1000);System.out.println("Hello");System.out.println(threadPoolExecutor.isTerminated());} catch (InterruptedException e) {throw new RuntimeException(e);}});}// 关闭线程池threadPoolExecutor.shutdown();try {if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {threadPoolExecutor.shutdownNow();}} catch (InterruptedException e) {threadPoolExecutor.shutdownNow();}}
}

1.2)类图结构与继承关系

ThreadPoolExecutor 类的类图:

在这里插入图片描述

继承关系ThreadPoolExecutor 继承了 AbstractExecutorServiceAbstractExecutorService 又实现了 ExecutorService 接口,然后 ExecutorService 实现了 Executor 接口。
在这里插入图片描述

其中,Executor 接口是 Java 并发框架中的一个核心接口,用于 将任务的提交任务的执行解耦。它的设计目标是提供一种标准的方法来执行提交的任务,而不需要关心任务是如何执行的(例如,使用单个线程、线程池、异步调用等),这个接口是一个函数式接口,里面只有一个 execute(Runnable command); 方法。

    /*** Executes the given command at some time in the future.  The command* may execute in a new thread, in a pooled thread, or in the calling* thread, at the discretion of the {@code Executor} implementation.*/void execute(Runnable command);

常见的和 Executor 接口有关的类有以下几个:

  • Executors 工具类,里面提供了静态的工厂方法来创建常用的 Executor 实现
  • 本节中讲的 ThreadPoolExecutor 是该接口最常用的实现,用于基于创建的线程池来执行任务
  • SingleThreadExecutor , 使用单个工作线程执行任务,任务按提交顺序执行。
  • **ScheduledThreadPoolExecutor ,**支持任务定时和周期性执行的 Executor 实现

1.3)关键属性

说完了类的继承关系,下面按照类图中的顺序讲解一下 ThreadPoolExecutor需要了解的属性:

其中的成员变量 ctl 是一个 Integer 类型的原子变量,用来记录线程池的状态和线程池中的线程个数,类似于前面的 ReentrantReadWriteLock 使用一个变量来存储两个信息。

其中的 mainLock 是独占锁,用来控制新增 Worker 线程操作的原子性。termination 是这个锁对应的一个 Condition,线程调用**termination.awaitNanos(nanos)** 方法的时候会处于阻塞的情况;再通过**termination.signalAll();** 等方法来唤醒线程。

Worker 类实现了 Runnable 接口,是具体承载任务的对象,其代理了 Thread 线程,通过静态代理的方式代理了线程的 run() 方法,使得线程池管理线程更加方便。它继承 AQS 自己实现了简单的不可重入锁;其中 state=0 表示锁未被获取的状态,state=1 表示已经被获取的状态,state=-1 是 Worker 的默认状态,创建线程的时候设置初始状态会将 state 设置为 -1,将其设置为 -1 的原因后面会提到;其中的变量 firstTask 记录这个工作线程执行的第一个任务,thread 是具体执行任务的线程。

DefaultThreadFactory 是线程工厂,通过使用线程工厂提供的 newThread 方法可以便捷的创建线程

  • poolNumber 是一个静态的原子变量,用来统计线程工厂的个数,没当线程工厂被实例化之后,会使用 CAS 操作使得原子变量做一个递增操作。
  • threadNumber 用来记录每个线程工厂创建了多少线程,这两个值也作为线程池和线程名称的一部分。

2)预备知识

在正式开始源码的阅读之前,我们需要先了解一些预备知识。

2.1)线程池状态及转换方式

线程池有如下几种状态:

  • RUNNING:接受新任务并且处理阻塞队列中的任务。
  • SHUTDOWN:拒绝新任务并且处理阻塞队列中的任务。
  • TIDYING:所有任务都执行完成(包括阻塞队列中的任务)后当前线程池活动线程数为 0,将要调用 terminated 方法。
  • TERMINATED:终止状态, terminated() 方法调用完成以后的状态。

它们之间的转换方式是这样的:

  1. RUNNINGSHUTDOWN:显示的调用 shutdown() 方法。
  2. RUNNING 或者 SHUTDOWNSTOP:显示的调用 shutdownNow() 方法的时候。
  3. SHUTDOWNTIDYING:当线程池和任务队列都为空的时候。
  4. TIDYINGTERMINATED:当 terminated() 方法执行完成的时候。

2.2)线程池的参数

线程池的参数有以下几种:

  • corePoolSize:线程池核心线程个数
  • workQueue:用于保存等待执行的任务阻塞队列,比如基于数组的有界 ArrayBlockingQueue、基于链表的无界 LinkedBlockingQueue、最多只有一个元素的同步队列 SynchronousQueue 以及优先级队列 PriorityBlockingQueue 等。
  • maximumPoolSize:线程池的最大线程数量
  • ThreadFactory:创建线程的工厂
  • RejectedExecutionHandler:饱和策略,当任务队列满并且线程个数达到最大容量的时候采取的策略,比如 AbortPolicy 抛出异常、CallerRunsPolicy 使用调用者所在的线程来运行任务、DiscardOldestPolicy 调用 poll 丢弃一个任务,执行当前的任务、以及 DiscardPolicy 默默丢弃不抛弃异常。
  • keeyAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置的状态,则这些闲置的线程存活的最大时间
  • TimeUnit:存活时间的时间单位

2.3)线程池的类型

线程池的类型有以下几种,可以通过 Executors 工具类提供的方法快速的创建

  • newFixedThreadPool:创建一个核心线程个数和最大线程个数都为 nThreads 的线程池,并且阻塞队列的长度为 Integer.MAX_VALUE。keeyAliveTIme 为 0,说明只要线程个数比核心线程个数多就执行回收的操作
    public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}// 使用自定义线程创建工厂public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory);}
  • newSingleThreadExecutor:常见一个核心线程个数和最大线程个数都为 1 的线程池,并且阻塞队列的长度为 Integer.MAX_VALUE。且 keeyAliveTime = 0,即只要线程个数比核心线程数多就回收。
    public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}// 使用线程工厂创建public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(),threadFactory));}
  • newCachedThreadPool:创建一个按需创建线程的线程池,初始的线程个数为 0,最多的线程个数为 Integer.MAX_VALUE,并且阻塞队列为同步队列。它的线程空闲存活时间为 60s。加入同步队列的任务会被马上执行,且同步队列中最多只有一个任务。
    public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}// 使用线程工厂创建public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>(),threadFactory);}

2.4)线程池的执行逻辑

线程池中任务的执行是依赖于 Worker 实例化对象的,它被创建后会存储在

private final HashSet<Worker> workers = new HashSet<Worker>();

中,Worker 是一个实现了 Runnable 接口的类,它的内部存储着一个真正执行任务的线程,它的创建方式是这样的:

        Worker(Runnable firstTask) {setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this); // 创建线程}Thread newThread(Runnable r);

将 this 也就是 Worker 对象本身传递给线程工厂来创建,当通过 start 方法启动这个线程的时候,其实执行的就是 Worker 中重写的 run 方法。Worker 类是实际执行任务的类,它的 run() 方法的逻辑就是通过循环不断的从任务队列 workQueue 中获取任务,然后执行。

线程池就是维护根据上面提到的,诸如核心线程数,存活时间等参数来维护 workers,实时的增加或者删除其中的线程;通过将任务加入到 workQueue 队列中来让这些 worker 来执行这些任务。

3)源码分析

3.1)线程池执行任务

这个方法的作用是根据线程池当前的状态来决定让这个任务立刻执行、添加到线程池还是。执行拒绝策略

ThreadPoolExecutor 的实现是一个生产消费模型,当用户添加任务到线程池相当于生产者生产元素,而 Worker 来执行任务相当于消费元素;这个方法的执行逻辑在源码的注释中已经写的很清楚了,下面是翻译后的内容:

  1. 如果运行的线程少于 corePoolSize,尝试启动一个新线程,并将给定的命令作为其第一个任务。调用 addWorker 会原子地检查运行状态和工作线程数量,从而防止错误报警导致不应增加线程的情况,通过返回 false 来阻止这种情况。
  2. 如果任务可以成功排队,那么我们仍然需要再次确认是否应该添加一个线程(因为自上次检查以来现有的线程可能已经终止)或者线程池在进入该方法之后已经关闭。因此,我们会重新检查状态,并在必要时回滚任务的排队操作(如果线程池已经停止),或者如果没有线程则启动一个新线程。
  3. 如果我们不能将任务排队,那么我们会尝试添加一个新线程。如果添加失败,我们就知道线程池已经关闭或已饱和,因此拒绝该任务。
    public void execute(Runnable command) {// (1)检查传入的参数,如果为 null 会抛出 NPE 异常if (command == null)throw new NullPointerException();// (2)获取当前线程池的状态 + 线程个数的组合值  int c = ctl.get();// (3)检查线程池的个数是否小于 corePoolSize,如果小于则开启新的线程if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}// (4)如果线程池处于 Running 状态,添加任务到阻塞队列if (isRunning(c) && workQueue.offer(command)) {// (4.1)二次检查int recheck = ctl.get();// 如果线程池的状态不是 RUNNING 则删除队列中的任务,执行拒绝策略if (! isRunning(recheck) && remove(command))reject(command);// (4.2)如果当前线程池为空,则添加一个线程else if (workerCountOf(recheck) == 0)addWorker(null, false);}// (5)阻塞队列满了,尝试添加新的线程,如果失败了就执行拒绝策略else if (!addWorker(command, false))reject(command);}

上面需要特别注意的是代码(5)的执行时机,也就是当代码 (4)执行失败的处于什么状态

此时向任务队列中添加任务失败,或者是线程池不处于 RUNNING 状态(除了 RUNNING 状态以外都无法添加任务或需要执行拒绝策略),此时执行这段代码来尝试重新开启一个线程来执行任务,如果此时仍然添加失败,我们就知道线程池已经关闭或已饱和,因此拒绝该任务。

3.2)添加 Worker

这个方法是用来添加一个新的 Worker 对象到 workers 中,参数为初始任务(firstTask)、是否是核心线程(core)。

在添加 Worker 的时候需要保证并发安全性,且需要实时的监测线程池的状态,并且还含有执行任务,修改参数等代码,所以长度会很长,这里分成两个部分来讲解;先来看第一部分,这一部分通过 CAS 操作尝试去修改 Worker 的个数,当修改成功后才会去执行真正的实例化代码。

    private boolean addWorker(Runnable firstTask, boolean core) {retry:for (;;) {int c = ctl.get();int rs = runStateOf(c);// 1)检查队列是否在必要的时候为空if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))return false;// 2)在循环中通过 CAS 来增加线程的个数for (;;) {int wc = workerCountOf(c);// 2.1)如果线程数目超过指定的上限,返回 falseif (wc >= CAPACITY ||wc >= (core ? corePoolSize : maximumPoolSize))return false;// 2.2)使用 CAS 来增加线程的个数if (compareAndIncrementWorkerCount(c))break retry;// 2.3)如果 CAS 失败了,先去检查线程池的状态是否变化,//     如果变化则跳到外层循环重新获取线程池状态,否则循环继续尝试 CAS。c = ctl.get();if (runStateOf(c) != rs)continue retry;}// 。。。 第二段代码}

代码(1)是检测线程池状态的代码,通过逻辑运算,可以将其转换成以下的格式

s >= SHUTDOWN && (rs != SHUTDOWN ||firstTask != null ||workQueue.isEmpty())

这段逻辑的含义是这样的:

  • 如果当前线程池状态 s 大于或等于 SHUTDOWN(即线程池已经开始关闭或已经关闭)。
  • 然后进一步检查以下情况之一:
    • 重新检查的状态 rs 不是 SHUTDOWN,表示线程池的状态可能已经改变。
    • firstTask 不为空,意味着有一个新的任务尝试提交。
    • 任务队列是空的。

当上述条件都满足时,通常表示线程池不应接受新的任务,因此会拒绝任务的提交。

然后,线程在循环中不断通过 CAS 操作来尝试修改 WorkCount 的值(ctl 的一部分),如果修改成功,则继续执行下一部分代码。

// 3)CAS 成功了boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {// 3.1)创建 workerw = new Worker(firstTask);final Thread t = w.thread;if (t != null) {// 3.2)加锁,防止并发问题final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {int rs = runStateOf(ctl.get());// 3.3)重新检查线程池的状态,线程池处于 SHUTDOWN 方法会停用if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {// 如果线程被启用,线程工厂创建的线程出了问题,直接抛出异常if (t.isAlive())throw new IllegalThreadStateException();// 3.4)添加一个任务执行单位workers.add(w);int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;workerAdded = true;}} finally {mainLock.unlock();}//  3.5)如果添加任务成功就执行任务if (workerAdded) {t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}

这一部分代码是成功通过 CAS 添加 Worker 的个数后执行的,但是现在任务还没有正式开始执行。在将 worker 添加到 workers 之前通过独占锁保证了线程安全。

最终,当添加成功的时候,就会启动工作线程。

3.3)工作线程执行

当用户提交任务到线程池之后,是通过 Worker 来执行的,Worker 是 ThreadPoolExecutor 中的一个内部类,它的构造方法是这样的:

        Worker(Runnable firstTask) {setState(-1); // 在调用 runWorkker 方法前禁止中断this.firstTask = firstTask;// 通过线程工厂来创建一个线程this.thread = getThreadFactory().newThread(this);}

在构造方法的时候设置 Worker 的状态为 -1,是为了避免 Worker 在调用 runWorker 方法之前被中断,当其他线程调用线程池 shutdownNow 的时候,如果 Worker 状态大于等于零则会被中断。这里设置了 -1 就不会被中断了。

而当执行 runWorker 方法的时候,会将其 state 设置为 0,此时就可以被中断了。

Worker 类实现了 Runnable 接口,也就是实现了 run() 方法,这个方法的内部调用的就是 runWorker() 方法来完成线程执行逻辑。

        /** Delegates main run loop to outer runWorker  */public void run() { runWorker(this); }

下面来看一下 runWorker 方法的具体实现:

    final void runWorker(Worker w) {Thread wt = Thread.currentThread();Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // 允许 Worker 被中断boolean completedAbruptly = true;try {// 循环执行任务,有任务的时候会一直循环while (task != null || (task = getTask()) != null) {w.lock();// (1)if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP))) &&!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 = null;// 统计当前的线程完成了多少任务w.completedTasks++;w.unlock(); }}completedAbruptly = false;} finally {// 清理工作processWorkerExit(w, completedAbruptly);}}

先去判断是否有任务需要执行,如果有就通过 task.run() 方法来执行(task 也是实现了 Runnable 接口的对象),在执行任务之前会 Worker 加上锁,这样说为了避免在执行任务期间,其他线程调用 shutdown 方法导致正在执行的任务被中断,shutdown 方法的逻辑的只中断被阻塞挂起的线程,这个在后面分析源码的时候会提到。

上面的 (1)代码是处理线程池的停止和线程的中断逻辑的,它的判断逻辑是这样的:

这个条件判断分为两部分:

  • runStateAtLeast(ctl.get(), STOP)检查线程池的状态是否至少是STOP状态,这意味着有请求停止线程池。
  • (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))检查当前线程是否已被中断,并且线程池状态也是STOPThread.interrupted()会清除当前线程的中断状态并返回之前中断的状态。 如果以上任意条件满足,并且工作线程wt尚未被中断(!wt.isInterrupted()),则执行下一行的中断操作。

当线程在循环中结束后,最终执行会清理工作,调用 processWorkerExit,其代码如下:

    private void processWorkerExit(Worker w, boolean completedAbruptly) {if (completedAbruptly)decrementWorkerCount();// 统计当前线程完成任务的个数,并且从工作集中删除当前的 Workerfinal ReentrantLock mainLock = this.mainLock;mainLock.lock();try {completedTaskCount += w.completedTasks;workers.remove(w);} finally {mainLock.unlock();}// 尝试设置线程状态为 TERMINATED,当前是 SHUTDOWN 状态并且工作// 队列为空的时候或者当前为 STOP 状态,线程池中没有活动线程tryTerminate();// 如果当前线程个数小于核心数目,增加新的 Workerint c = ctl.get();if (runStateLessThan(c, STOP)) {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);}}

代码中先加入全局锁,然后统计线程池完成任务的个数,最后删除 Worker 对象。

然后尝试将线程池的状态设置为 TERMINATED,最终检测当前线程池中的线程个数是否小于核心线程数,如果是则会新增一个工作线程,并将其 firstTask 设置为 null。

3.4)shutdown 操作

调用 shutdown 方法后,线程池不会再接受新的任务了,但是工作队列中的任务还是会执行的,该方法会立刻返回,不会等待队列任务完成再返回。

    public void shutdown() {// 锁,注意这个锁是 ThreadPoolExecutor 中的锁final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {// 检查当前线程是否有权限执行checkShutdownAccess();// 设置状态为 SHUTDOWN,如果已经是则直接返回advanceRunState(SHUTDOWN);// 给 Workers 设置中断标志interruptIdleWorkers();onShutdown(); // hook for ScheduledThreadPoolExecutor} finally {mainLock.unlock();}// 尝试中断tryTerminate();}

其中比较关键的方法是 advanceRunState() 方法和 interruptIdleWorkers() 两个方法,下面来分别看一下它们的实现:

    private void advanceRunState(int targetState) {for (;;) {int c = ctl.get();// 当前状态已经比 SHUTDOWN 还要高,也就是以下三种情况// STOP、TIDYING、TERMINATEDif (runStateAtLeast(c, targetState) ||ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))break;}}// 用于判断线程池的运行状态(c)是否至少达到了给定的状态级别(s)private static boolean runStateAtLeast(int c, int s) {return c >= s;}

上面的方法中先判断当前状态是否已经达到了 SHUTDOWN,如果是直接返回,否则就通过 CAS 操作将其状态设置为 SHUTDOWN。

    private void interruptIdleWorkers(boolean onlyOne) {// 上锁final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers) {Thread t = w.thread;// 如果线程没有被中断,并且获取锁成功,也就是说这个线程没有执行任务if (!t.isInterrupted() && w.tryLock()) {try {// 尝试中断线程t.interrupt();} catch (SecurityException ignore) {} finally {// unlock worker,将其 state 设置为 0w.unlock();}}if (onlyOne)// 如果只需要中断一个线程的话,释放锁break;}} finally {// 释放锁mainLock.unlock();}}

上面的方法接收一个布尔值参数 onlyOne 表明中断一个线程还是全部,然后在循环工作集,中断未被中断的线程,在中断线程的时候会使用 Worker 的独占锁,而正在执行任务的线程会持有自己的锁,无法被释放,所以这里其实只中断了未执行任务的锁。

3.5)shutdownNow() 方法

看完了不会中断执行任务线程的 shutdown 方法, shutdownNow 方法则会中断所有线程,无论它们是否在执行任务。

这个方法与前面的 shutdown 方法的区别就是调用的方法不通,在这里中断线程调用的是 interruptWorkers() ,会中断所有的线程。

    public List<Runnable> shutdownNow() {List<Runnable> tasks;final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers();tasks = drainQueue();} finally {mainLock.unlock();}tryTerminate();return tasks;}

这里不会去尝试获取线程的锁,而是直接执行中断的方法,这里如果线程的 state 小于零(未启动的时候初始值为 -1),此时不会去中断线程。

    private void interruptWorkers() {final ReentrantLock mainLock = this.mainLock;mainLock.lock();try {for (Worker w : workers)w.interruptIfStarted();} finally {mainLock.unlock();}}void interruptIfStarted() {Thread t;if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {try {t.interrupt();} catch (SecurityException ignore) {}}}

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

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

相关文章

AI论文速读 | 2024[ICML]FlashST:简单通用的流量预测提示微调框架

题目&#xff1a; FlashST: A Simple and Universal Prompt-Tuning Framework for Traffic Prediction 作者&#xff1a;Zhonghang Li, Lianghao Xia&#xff08;夏良昊&#xff09;, Yong Xu&#xff08;徐勇&#xff09;, Chao Huang 机构&#xff1a;华南理工大学&#xf…

【SpringBoot】SpringBoot整合RabbitMQ消息中间件,实现延迟队列和死信队列

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 目录 一、&#x1f525;死信队列 RabbitMQ的工作模式 死信队列的工作模式 二、&#x1f349;RabbitMQ相关的安装 三、&#x1f34e;SpringBoot引入RabbitMQ 1.引入依赖 2.创建队列和交换器 2.1 变量声明 2.2 创建…

在群晖上通过Docker部署DB-GPT

最近一直有网友在后台私信&#xff0c;发的内容高度统一&#xff0c;只有后面 8 位数字不一样&#xff0c;都是 &#xff03;22232 xxxxxxxx&#xff0c;有谁知道是什么意思吗&#xff1f;在我印象中&#xff0c;这是第二次这么大规模的发类似的字符串了 什么是 DB-GPT ? DB-G…

Linux lvm卷扩容之SSM

介绍 SSM&#xff08;System Storage Manager&#xff09;是系统存储管理器&#xff0c;它是一种统一的命令行界面&#xff0c;用于管理各种存储设备。通过SSM&#xff0c;用户可以方便地管理、配置和监控存储系统。检查关于可用硬驱和LVM卷的信息。显示关于现有磁盘存储设备、…

O2OA(翱途)开发应用平台(v9)开发实战(3)-如何做信息发布

内容管理就是用来发布信息的&#xff0c;比如说发布单位的内部信息&#xff1a;像公司新闻、通知公告、规章制度等等。 接下来我们来介绍一下如何创建&#xff0c;比如我要创建一个栏目&#xff0c;专门用来发布公司的规章制度 需求 规章制度 首先从菜单打开“内容管理设置…

平衡二叉树AVL

平衡二叉树是一种特殊的二叉查找树&#xff0c;其中每个节点的左右子树的高度差不超过1。这种树的平衡性质使其在多种操作下保持较高的效率。 平衡二叉树的定义与性质 严格定义&#xff1a;在平衡二叉树中&#xff0c;任一节点的两个子树的高度最大差别为一&#xff0c;这使得…

Linux卸载RocketMQ教程【带图文命令巨详细】

巨详细Linux卸载RocketMQ教程 #查询rocketmq进程 ps -ef | grep rocketmq #杀掉相关进程 kill -9 进程id #查找安装目录 find / -name runbroker.sh #删除rocketMQ目录 rm -rf 安装目录框起来的就是进程id&#xff0c;全部杀掉 这里就是我的安装目录&#xff0c;我的删除命令…

SwiftUI五视图动画和转场

代码下载 使用SwiftUI可以把视图状态的改变转成动画过程&#xff0c;SwiftUI会处理所有复杂的动画细节。在这篇中&#xff0c;会给跟踪用户徒步的图表视图添加动画&#xff0c;使用animation(_:)修改器给一个视图添加动画效果非常容易。 下载起步项目并跟着本篇教程一步步实践…

AI 写高考作文丨10 款大模型 “交卷”,实力水平如何?

前言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是逐渐融入我们日常生活的实用工具。从智能语音助手到自动驾驶汽车&#xff0c;从智能家居系统到精准医疗诊断&#xff0c;AI技术正以其强大的计算能力和数…

Rust基础学习-Rust宏

Rust中的宏是生成另一段代码的一段代码。可以根据输入生成代码&#xff0c;简化重复模式&#xff0c;使得代码更加简洁。比如我们一直在用的println!,vec!,panic!都是宏。 创建宏 可以使用macro_rules!创建一个宏&#xff1a; macro_rules! macro_name {(...) > {...} }这…

c#与汇川plc通信 使用官网API库

前言 上位机开发中有时会要求与PLC进行通信&#xff0c;汇川官网也有好用的API库方便大家使用。记录一下开发过程。 1.下载资料 汇川官网地址&#xff1a;汇川技术 - 推进工业文明 共创美好生活 打开后选择&#xff1a;服务与支持-》资料下载-》 资料下载 这里可以直接搜索&am…

C++学习插曲:“name“的初始化操作由“case“标签跳过

问题 "name"的初始化操作由"case"标签跳过 问题代码 case 3: // 3、删除联系人string name;cout << "请输入删除联系人姓名&#xff1a;" << endl;cin >> name;if (isExistPerson(&abs, name) -1){cout << "…

【刷题篇】分治-归并排序

文章目录 1、排序数组2、交易逆序对的总数3、计算右侧小于当前元素的个数4、翻转对 1、排序数组 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 class Solution { public:vector<int> tmp;void mergeSort(vector<int>& nums,int left,int right){…

cocos creator3.7版本拖拽事件处理

前言&#xff1a;网上能找到的资料都太落后了&#xff0c;导致哥们用AI去写&#xff0c;全是瞎B写&#xff0c;版本都不对。贴点实际有用的。别老捣鼓你那破convertToNodeSpaceAR或者convertToNodeSpace了。 核心代码 touch.getDeltaX() touch.getDeltaY() 在cocoscreator3…

python-自幂数判断

[题目描述]&#xff1a; 自幂数是指&#xff0c;一个N 位数&#xff0c;满足各位数字N 次方之和是本身。例如&#xff0c;153153 是 33 位数&#xff0c;其每位数的 33 次方之和&#xff0c;135333153135333153&#xff0c;因此 153153 是自幂数&#xff1b;16341634 是 44 位数…

简单快速设置Windows和Ubuntu双系统双引导

一、参考资料 Windows和Ubuntu双系统安装教程 二、设置引导 1. 安装EasyBCD 下载并安装 EasyBCD 2. 设置Windows引导 3. 设置Ubuntu引导 4. 启动系统 遇到这种情况&#xff0c;直接Enter回车。 三、修复引导 如果引导区损坏&#xff0c;导致无法进入系统&#xff0c;可以…

FuTalk设计周刊-Vol.041

&#x1f525;AI漫谈 热点捕手 1、国产GPTs来了&#xff0c;基于智谱第4代大模型 全自研第四代基座大模型GLM-4&#xff0c;且所有更新迭代的能力全量上线。GLM-4性能相比GLM-3提升60%&#xff0c;逼近GPT-4&#xff08;11月6日最新版本效果&#xff09;。而同时推出的GLM-4-…

【漏洞复现】多客圈子论坛系统 httpGet 任意文件读取漏洞

0x01 产品简介 多客圈子论坛系统是一种面向特定人群或特定话题的社交网络&#xff0c;它提供了用户之间交流、分享、讨论的平台。在这个系统中&#xff0c;用户可以创建、加入不同的圈子&#xff0c;圈子可以是基于兴趣、地域、职业等不同主题的。用户可以在圈子中发帖、评论、…

算法分析与设计期末考试复习(更新ing)

重点内容&#xff1a; 绪论&#xff1a; 简单的递推方程求解 1.19(1)(2) 、 教材例题 多个函数按照阶的大小排序 1.18 分治法&#xff1a; 分治法解决芯片测试问题 计算a^n的复杂度为logn的算法&#xff08;快速幂&#xff09; 分治法解决平面最近点对问…

让 AI 写高考作文丨10 款大模型 “交卷”,实力水平如何?

文章部分素材来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 前言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是逐渐融入我们日常生活的实用工具。从智能语音助手到自动驾驶汽车&#xff0c…