java线程池基础

目录

  • 一、线程池基础概念
    • 1.1 什么是线程池?
    • 1.2 为什么要用线程池?
    • 1.3 线程池的工作原理
  • 二、java中的线程池
    • 2.1 线程池真正实现类 ThreadPoolExecutor
      • 2.1.2 构造器
      • 2.1.3 参数
        • 2.1.3.1 workQueue
        • 2.1.3.2 threadFactory
        • 2.1.3.3 handler
    • 2.2 线程池的使用
      • 2.2.1 newCachedThreadPool
      • 2.2.2 newFixedThreadPool
      • 2.2.3 newScheduledThreadPool
      • 2.2.4 newSingleThreadExecutor
    • 2.3 方法对比
    • 2.4 使用流程
  • 三、内部原理逻辑
  • 四、参考链接


一、线程池基础概念

1.1 什么是线程池?

线程池的基础概念

线程池(Thread Pool)是一种用于管理和复用线程的机制,它包含一组线程以及一些管理这些线程的方法。线程池在程序启动时创建一定数量的线程,并在需要执行任务时从线程池中获取线程来执行任务,任务执行完毕后线程并不立即销毁,而是返回线程池中等待下次任务执行。这样可以避免频繁地创建和销毁线程,提高系统的性能和效率。

1.2 为什么要用线程池?

为什么要用线程池?其实就是线程池优势在哪里?

1.降低资源消耗:线程的创建和销毁是一项消耗资源较大的操作,使用线程池可以减少这种开销。
2.提高响应速度:线程池中的线程可以立即执行任务,无需等待线程创建,提高系统的响应速度。
3.控制并发线程数量:通过线程池可以限制并发线程的数量,避免系统资源被耗尽。
4.提高系统稳定性:合理使用线程池可以避免因为线程过多导致系统崩溃的情况发生。

1.3 线程池的工作原理

线程池具体是如何工作的?
按照一个线程的运行过程,大致可以明白线程池的工作原理:

1.线程池初始化:根据配置信息(如核心线程数、最大线程数、任务队列等)创建一定数量的线程,并将这些线程保存在线程池中。
2.任务提交:当有任务需要执行时,线程池会从线程池中取出一个空闲线程来执行任务。
3.任务队列:如果线程池中的线程都在执行任务,新的任务会被放入任务队列中等待执行。
4.线程复用:当线程池中的线程空闲时,会从任务队列中取出任务来执行,实现线程的复用。
5.线程销毁:根据配置的参数,当线程空闲时间超过设定的时间时,空闲线程会被销毁,以节省资源。


二、java中的线程池

2.1 线程池真正实现类 ThreadPoolExecutor

2.1.2 构造器

参数不同,但最终都会调到最下方的构造器中

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

    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.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

2.1.3 参数

  • corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
  • keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
  • unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
  • workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
  • threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式。
  • handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。

corePoolSize、maximumPoolSize、keepAliveTime、unit不用太多说明,都是数字或者true、false,根据自己的需求填充对应的参数即可。

2.1.3.1 workQueue

用于指定线程池中保存等待执行任务的阻塞队列。在多线程编程中,线程池的工作原理之一就是通过任务队列来保存待执行的任务,以便线程池中的线程可以按需取出任务执行。

常见的阻塞队列类型包括:

LinkedBlockingQueue:基于链表实现的无界阻塞队列,当任务队列已满时会一直阻塞等待直到有空间。
ArrayBlockingQueue:基于数组实现的有界阻塞队列,需要指定队列的容量,当队列已满时会阻塞等待。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等待另一个线程的移除操作,常用于直接传递任务。
PriorityBlockingQueue:具有优先级的阻塞队列,根据元素的优先级顺序取出任务。
DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。

有界队列与无界队列

有界队列:
指定了队列的最大容量,当队列已满时,新提交的任务将会被阻塞等待,直到队列中有空间可以存放任务。
有界队列可以避免无限制地接受任务导致内存溢出的风险,可以控制系统资源的使用。
使用有界队列,当队列饱和时并超过最大线程数时就会执行拒绝策略。
无界队列
无界队列没有指定固定的容量,可以不断地接受新的任务,直到系统资源耗尽。
无界队列可以保证任务不会被拒绝,但可能会导致内存溢出或系统资源耗尽的风险。
使用无界队列,因为任务队列永远都可以添加任务,所以设置 maximumPoolSize 没有任何意义。

选择合适的阻塞队列类型可以根据实际场景来决定,例如需要控制任务的执行顺序、任务的优先级等。阻塞队列的选择也会影响线程池的性能和行为,因此需要根据具体需求进行选择。

2.1.3.2 threadFactory

用于创建新线程的工厂。在线程池中,当需要创建新的线程来执行任务时,会使用threadFactory来创建线程实例。
自定义threadFactory可以通过实现ThreadFactory接口来实现,重写newThread方法来创建新线程。例如:

ThreadFactory customThreadFactory = new ThreadFactory() {
    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r);
        thread.setName("CustomThread-" + thread.getId());
        thread.setPriority(Thread.MAX_PRIORITY);
        return thread;
    }
};

在实际应用中,通过自定义threadFactory可以实现一些特定的需求,如统一设置线程名称、设置线程优先级、设置线程为守护线程等。这样可以更好地管理线程池中的线程,使其符合系统需求和调优要求。

Executors 框架已经为我们实现了一个默认的线程工厂。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    public static ThreadFactory defaultThreadFactory() {
        return new DefaultThreadFactory();
    }
    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            @SuppressWarnings("removal")
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
2.1.3.3 handler

用于指定当线程池和任务队列都已满时,新的任务如何被拒绝执行的策略。在多线程编程中,当线程池无法接受新任务时,就会触发拒绝策略来处理这种情况。

常见的拒绝策略包括:

AbortPolicy:默认的拒绝策略,会直接抛出RejectedExecutionException异常,通知调用者任务被拒绝。
CallerRunsPolicy:将任务交给调用线程来执行,即在调用线程中执行被拒绝的任务。
DiscardPolicy:直接丢弃被拒绝的任务,不做任何处理。
DiscardOldestPolicy:丢弃队列中等待时间最长的任务,然后尝试重新提交新的任务。

自定义拒绝策略:可以实现RejectedExecutionHandler接口来自定义拒绝策略,根据具体需求来处理被拒绝的任务。
选择合适的拒绝策略可以根据实际需求来决定,例如希望通知调用者任务被拒绝、希望在调用线程中执行被拒绝的任务、或者希望丢弃一些任务以保证系统的稳定性等。

2.2 线程池的使用

一大堆的概念看完了?那么线程池具体该如何使用呢?
其实java内部通过Executors提供了四种线程池,而内部都是使用了ThreadPoolExecutor

2.2.1 newCachedThreadPool

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

创建一个可缓存的线程池,适用于执行很多短期异步任务的场景。
如果线程池中有空闲线程,则重用空闲线程;如果没有空闲线程,则创建新线程。
当线程空闲时间超过60秒时,会被回收,适用于执行大量短期异步任务的情况。

2.2.2 newFixedThreadPool

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

创建一个固定大小的线程池,适用于控制线程数量的场景。
线程池中始终保持固定数量的工作线程,当有任务提交时,如果有空闲线程则立即执行,否则任务进入队列等待。
适用于需要限制线程数量的情况,避免线程过多导致资源消耗过大。

2.2.3 newScheduledThreadPool

    public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

    public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

创建一个固定大小的线程池,支持定时及周期性任务执行。
可以设置核心线程数,适用于需要定时执行任务的场景,如定时任务、周期性任务等。
可以通过schedule方法或scheduleAtFixedRate方法执行定时任务。

2.2.4 newSingleThreadExecutor

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

创建一个只有一个工作线程的线程池,适用于需要顺序执行任务的场景。
线程池中只有一个工作线程,保证任务按照提交的顺序依次执行。
适用于需要按顺序执行任务、避免并发执行的场景。

    public static void main(String[] args) {
        cachedThreadPoolTest();
        fixedThreadPoolTest();
        ScheduledExecutorServiceTest();
        singleThreadExecutorTest();
    }

    public static void cachedThreadPoolTest() {
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        cachedThreadPool.execute(() -> {
            System.out.println("Task 1");
        });
        cachedThreadPool.execute(() -> {
            System.out.println("Task 2");
        });
    }


    public static void fixedThreadPoolTest() {
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        fixedThreadPool.execute(() -> {
            System.out.println("Task 1");
        });
        fixedThreadPool.execute(() -> {
            System.out.println("Task 2");
        });
    }

    public static void ScheduledExecutorServiceTest() {
        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(2);
        scheduledThreadPool.schedule(() -> {
            System.out.println("Scheduled Task 1");
        }, 3, TimeUnit.SECONDS);
        scheduledThreadPool.scheduleAtFixedRate(() -> {
            System.out.println("Scheduled Task 2");
        }, 0, 5, TimeUnit.SECONDS);
    }

    public static void singleThreadExecutorTest() {
        ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
        singleThreadExecutor.execute(() -> {
            System.out.println("Task 1");
        });
        singleThreadExecutor.execute(() -> {
            System.out.println("Task 2");
        });
    }

在这里插入图片描述

2.3 方法对比

方法特点优点缺点适用场景
newCachedThreadPool线程池中的线程数量可以动态调整。能够根据实际需求动态调整线程数量,节省资源。可能会因为线程数量过多导致系统资源消耗过大。适用于执行大量短期异步任务的场景。
newFixedThreadPool线程池中的线程数量是固定的。能够限制线程数量,避免线程过多导致资源消耗过大。可能会因为线程数量有限而无法满足高并发需求。适用于控制线程数量的场景。
newScheduledThreadPool支持定时及周期性任务执行,可以设置核心线程数,最大线程数和线程空闲时间等参数。能够方便地执行定时任务、周期性任务等。可能会因为定时任务过多导致线程池负载过重。适用于需要定时执行任务的场景。
newSingleThreadExecutor只有一个工作线程的线程池能够保证任务按照提交的顺序依次执行,避免并发执行。可能会因为只有一个线程而无法满足高并发需求。适用于需要顺序执行任务的场景。

2.4 使用流程

// 1. 创建线程池
   // 创建时,通过配置线程池的参数,从而实现自己所需的线程池
   Executor threadPool = new ThreadPoolExecutor(
                                              CORE_POOL_SIZE,
                                              MAXIMUM_POOL_SIZE,
                                              KEEP_ALIVE,
                                              TimeUnit.SECONDS,
                                              sPoolWorkQueue,
                                              sThreadFactory
                                              );
    // 注:在Java中,已内置4种常见线程池,下面会详细说明

// 2. 向线程池提交任务:execute()
    // 说明:传入 Runnable对象
       threadPool.execute(new Runnable() {
            @Override
            public void run() {
                ... // 线程执行任务
            }
        });

// 3. 关闭线程池shutdown() 
  threadPool.shutdown();
  
  // 关闭线程的原理
  // a. 遍历线程池中的所有工作线程
  // b. 逐个调用线程的interrupt()中断线程(注:无法响应中断的任务可能永远无法终止)

  // 也可调用shutdownNow()关闭线程:threadPool.shutdownNow()
  // 二者区别:
  // shutdown:设置 线程池的状态 为 SHUTDOWN,然后中断所有没有正在执行任务的线程
  // shutdownNow:设置 线程池的状态 为 STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
  // 使用建议:一般调用shutdown()关闭线程池;若任务不一定要执行完,则调用shutdownNow()

三、内部原理逻辑

在这里插入图片描述
此图来自大神的简书
Android多线程:线程池ThreadPool全面解析


四、参考链接

Android多线程:线程池ThreadPool全面解析
java线程池使用最全详解
Java 多线程:彻底搞懂线程池

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

超详细解析:在执行一条SQL语句期间发生了什么?

目录 前言MySQL的执行流程Server层连接器查询缓存词法分析器预处理优化器执行器 引擎层具体流程为什么需要redologredolog的组成redolog如何提高性能&#xff1f;redo log与binlog区别 总结 前言 我们学习MySQL时&#xff0c;首先第一个接触到的就是SQL语句了&#xff0c;那么…

MD5算法:密码学中的传奇

title: MD5算法&#xff1a;密码学中的传奇 date: 2024/3/15 20:08:07 updated: 2024/3/15 20:08:07 tags: MD5起源算法原理安全分析优缺点比较技术改进示例代码应用趋势 MD5算法起源&#xff1a; MD5&#xff08;Message Digest Algorithm 5&#xff09;算法是由MIT的计算机…

Linux之shell条件测试

华子目录 用途基本语法格式示例 文件测试参数示例 整数测试作用操作符示例~&#xff1a;检查左侧内容是否匹配右侧的正则表达式 案例分析逻辑操作符符号示例 命令分隔符&>&#xff1a;不管成功与否&#xff0c;都将信息写进黑洞中 用途 为了能够正确处理shell程序运行过…

django开发流式格式后的在nginx的部署的记录

关键记录. django上传代码要导出配置 pip freeze > requirements.txt 这个很关键。后面部署直接读取的 关键记录. django上传代码要导出配置 pip freeze > requirements.txt 这个很关键。后面部署直接读取的 关键记录. django上传代码要导出配置 pip freeze > require…

Python 基础语法:基本数据类型(字典)

为什么这个基本的数据类型被称作字典呢&#xff1f;这个是因为字典这种基本数据类型的一些行为和我们日常的查字典过程非常相似。 通过汉语字典查找汉字&#xff0c;首先需要确定这个汉字的首字母&#xff0c;然后再通过这个首字母找到我们所想要的汉字。这个过程其实就代表了…

Unity的AssetBundle资源运行内存管理的再次深入思考

大家好&#xff0c;我是阿赵。   这篇文章我想写了很久&#xff0c;是关于Unity项目使用AssetBundle加载资源时的内存管理的。这篇文章不会分享代码&#xff0c;只是分享思路&#xff0c;思路不一定正确&#xff0c;欢迎讨论。   对于Unity引擎的资源内存管理&#xff0c;我…

调皮的String及多种玩法(下部)

&#x1f468;‍&#x1f4bb;作者简介&#xff1a;&#x1f468;&#x1f3fb;‍&#x1f393;告别&#xff0c;今天 &#x1f4d4;高质量专栏 &#xff1a;☕java趣味之旅 欢迎&#x1f64f;点赞&#x1f5e3;️评论&#x1f4e5;收藏&#x1f493;关注 &#x1f496;衷心的希…

0101插入排序-算法基础-算法导论第三版

文章目录 一 插入排序二 循环不变式与插入排序的正确性三 伪代码中的一些约定四 Java代码实现插入排序结语 一 插入排序 输入&#xff1a; n n n个数订单一个序列 ( a 1 , a 2 , ⋯ , a n ) (a_1,a_2,\cdots,a_n) (a1​,a2​,⋯,an​). **输出&#xff1a;**输入序列的一个排…

【how2j练习题】HTML部分综合练习

练习题 1 <html><h1>英雄联盟 &#xff08;电子竞技类游戏&#xff09;</h1> <p> <strong>《英雄联盟》</strong>&#xff08;简称lol&#xff09;是由美国<i>Riot Games</i>开发&#xff0c;中国大陆地区由腾讯游戏运营的网络…

openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优

文章目录 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优244.1 统计信息调优244.1.1 统计信息调优介绍244.1.2 实例分析&#xff1a;未收集统计信息导致查询性能差 openGauss学习笔记-244 openGauss性能调优-SQL调优-典型SQL调优点-统计信息调优…

4.10.CVAT——3D对象标注

文章目录 1. 创建任务2. 3D 任务工作区3.标准 3D 模式 Standard 3D mode4. 用长方体进行注释4.1. 用shapes进行注释4.2. 使用长方体进行跟踪Tracking 使用 3D 注释工具来标记 3D 对象和场景&#xff0c;例如车辆、建筑物、景观等。 1. 创建任务 要创建 3D 任务&#xff0c;您必…

快速从0-1完成聊天室开发——环信ChatroomUIKit功能详解

聊天室是当下泛娱乐社交应用中最经典的玩法&#xff0c;通过调用环信的 IM SDK 接口&#xff0c;可以快速创建聊天室。如果想根据自己业务需求对聊天室应用的 UI界面、弹幕消息、礼物打赏系统等进行自定义设计&#xff0c;最高效的方式则是使用环信的 ChatroomUIKit 。 文档地址…

面试题手撕篇

参考博客 开始之前&#xff0c;理解递归 手写 浅拷贝 function shallow(target){if(target instanceof Array){return [...resObj]}else{return Object.assign({},target);} }手写深拷贝 const _sampleDeepClone target > {// 补全代码return JSON.parse(JSON.stringify…

mybatis源码阅读系列(一)

源码下载 mybatis 初识mybatis MyBatis 是一个优秀的持久层框架&#xff0c;它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解用于配置和原始映射&#xff0c;将接口和 Java 的…

UE4_调试工具_绘制调试球体

学习笔记&#xff0c;仅供参考&#xff01; 效果&#xff1a; 步骤&#xff1a; 睁开眼睛就是该变量在此蓝图的实例上可公开编辑。 勾选效果&#xff1a;

【小白刷leetcode】第15题

【小白刷leetcode】第15题 动手刷leetcode&#xff0c;正在准备蓝桥&#xff0c;但是本人算法能力一直是硬伤。。。所以做得一直很痛苦。但是不熟练的事情像练吉他一样&#xff0c;就需要慢速&#xff0c;多练。 题目描述 看这个题目&#xff0c;说实在看的不是很懂。索性我们直…

GUROBI建模之非线性约束的处理

官方文档 目录 官方文档&#xff1a;GRBModel.AddGenConstrXxx() - Gurobi Optimization 数学规划的约束类型 基本约束(fundamental constraints)&#xff1a; 通用约束(general constraints): 1. GUROBI求解器有针对这类约束的函数&#xff0c;直接调用这类函数即可 2.…

Python-GIS分析之地理数据空间聚类

地理空间数据聚类是空间分析和地理信息系统(GIS)领域的一项关键技术。这种方法对于理解地理数据固有的空间模式和结构、促进城市规划、环境管理、交通和公共卫生等各个领域的决策过程至关重要。本文探讨了地理空间数据聚类的概念、方法、应用、挑战和未来方向。 当模式出现…

音频切割如何操作?剪辑音乐入门教程

随着数字音乐时代的来临&#xff0c;音频编辑和音乐剪辑成为了越来越多人的必备技能。无论是想要制作个人音乐作品&#xff0c;还是想要为视频添加背景音乐&#xff0c;了解如何切割和剪辑音频都是非常重要的。本文将为你提供一份音频切割和音乐剪辑的入门教程&#xff0c;帮助…

13-操作符(初识)

课前小技巧&#xff1a;VS中&#xff0c;想要复制哪一行&#xff0c;直接把鼠标放在哪一行&#xff0c;CtrlC即可&#xff0c;CtrlV直接自动复制到下一行 C语言非常灵活&#xff1a;C语言提供了非常丰富的操作符&#xff0c;使用起来比较灵活 13-1 算术操作符 - * / % 这…