并发编程之----线程池ThreadPoolExecutor,Excutors的使用及其工作原理

news2024/11/15 4:47:53

当前:并发编程之----线程池ThreadPoolExecutor,Excutors的使用及其工作原理

Redis高级----主从、哨兵、分片、脑裂原理-CSDN博客

计算机网络--面试知识总结一

计算机网络-----面试知识总结二

计算机网络--面试总结三(Http与Https)

计算机网络--面试总结四(HTTP、RPC、WebSocket、SSE)-CSDN博客

​​​​​​知识积累之ThreadLocal---InheritableThreadLocal总结

ThreadPoolExecutor

存放线程的容器:

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

构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)

参数介绍:

  • corePoolSize:核心线程数,定义了最小可以同时运行的线程数量
  • maximumPoolSize:最大线程数,当队列中存放的任务达到队列容量时,当前可以同时运行的数量变为最大线程数,创建线程并立即执行最新的任务,与核心线程数之间的差值又叫救急线程数
  • keepAliveTime:救急线程最大存活时间,当线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等到 keepAliveTime 时间超过销毁
  • unit:keepAliveTime 参数的时间单位
  • workQueue:阻塞队列,存放被提交但尚未被执行的任务
  • threadFactory:线程工厂,创建新线程时用到,可以为线程创建时起名字
  • handler:拒绝策略,线程到达最大线程数仍有新任务时会执行拒绝策略
    RejectedExecutionHandler 下有 4 个实现类:
    • AbortPolicy:让调用者抛出 RejectedExecutionException 异常,默认策略
    • CallerRunsPolicy:让调用者运行的调节机制,将某些任务回退到调用者,从而降低新任务的流量即只要线程池没有关闭,就由提交任务的当前线程处理。(比如主线程中调用的线程池处理任务,然后当最大线程都在工作,工作队列也满了,那么执行该拒绝策略就是,被拒绝的任务回到调用线程池的主线程来进行处理
      • 应用场景:一般在不允许失败的、对性能要求不高、并发量较小的场景下使用,因为线程池一般情况下不会关闭,也就是提交的任务一定会被运行,但是由于是调用者线程自己执行的,当多次提交任务时,就会阻塞后续任务执行,性能和效率自然就慢了。
    • DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常
    • DiscardOldestPolicy:放弃队列中最早的任务,把当前任务加入队列中尝试再次提交当前任务
      • 使用场景:这个策略还是会丢弃任务,丢弃时也是毫无声息,但是特点是丢弃的是老的未执行的任务,而且是待执行优先级较高的任务。基于这个特性,我能想到的场景就是,发布消息,和修改消息,当消息发布出去后,还未执行,此时更新的消息又来了,这个时候未执行的消息的版本比现在提交的消息版本要低就可以被丢弃了。因为队列中还有可能存在消息版本更低的消息会排队执行,所以在真正处理消息的时候一定要做好消息的版本比较。

补充:其他框架拒绝策略

    • Dubbo:在抛出 RejectedExecutionException 异常前记录日志,并 dump 线程栈信息,方便定位问题
    • Netty:创建一个新线程来执行任务
    • ActiveMQ:带超时等待(60s)尝试放入队列
    • PinPoint:它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略

工作原理:
  1. 创建线程池,这时没有创建线程(懒惰),等待提交过来的任务请求,调用 execute 方法才会创建线程
  1. 当调用 execute() 方法添加一个请求任务时,线程池会做如下判断:
    • 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务
    • 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列
    • 如果这时队列满了且正在运行的线程数量还小于 maximumPoolSize,那么会创建非核心线程立刻运行这个任务,对于阻塞队列中的任务不公平。这是因为创建每个 Worker(线程)对象会绑定一个初始任务,启动 Worker 时会优先执行(可以理解为将其理解为核心线程和救急线程,核心线程一旦调用了execute方法,那么就会被创建并且不被销毁,而救急线程只有当阻塞队列满了之后还有任务来才创建,并且当创建出来的线程后面没有任务执行时,超过存活时间,就会被销毁)
    • 如果队列满了且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会启动饱和拒绝策略来执行
  1. 当一个线程完成任务时,会从队列中取下一个任务来执行
  1. 当一个线程空闲超过一定的时间(keepAliveTime)时,线程池会判断:如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后最终会收缩到 corePoolSize 大小

注意:救急线程的前提是有界队列的,如果使用的队列不是有界队列那么创建的就都是核心线程

注意:当有新任务来了,此时有现成处于空闲状态,那么新任务一定是有限被空闲线程所处理的,而不是将新任务放入队列中,然后再将队首任务进行消费,相当于不管队列满不满,只要有新任务来,如果核心线程有空闲,那么有限将新任务由核心线程来处理,而不是放入队列中

自定义拒绝策略

因为在自定义线程池的时候最后一个参数就是拒绝策略,其是一个接口,RejectedExcutionHandler ,那么我们自定义异常就可以直接实现该接口就可以了

实现

public class Pool {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 50,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10), new MyThread(), 
                new RejectedExecutionHandler() {
            @Override
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.out.println(r+"被拒绝");

            }
        });
        for (int i = 0; i < 100; i++) {
            int j=i;
            pool.execute(()->{
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName()+"--->"+j);
            });
        }
        pool.shutdown();
    }
}
//自定义线程工厂,用于命令线程
class MyThread implements ThreadFactory{
    AtomicInteger cnt=new AtomicInteger(0);

    @Override
    public Thread newThread(Runnable r) {
        Thread thread = new Thread(r, ("线程" + cnt.incrementAndGet()));

        return thread;
    }
}

Executors

Executors 提供了四种线程池的创建:

  • newCachedThreadPool
  • newFixedThreadPool
  • newSingleThreadExecutor
  • newScheduledThreadPool

newFixedThreadPool:创建一个拥有 n 个线程的线程池
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

    • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间
    • LinkedBlockingQueue 是一个单向链表实现的阻塞队列,默认大小为 Integer.MAX_VALUE,也就是无界队列,可以放任意数量的任务,在任务比较多的时候会导致 OOM(内存溢出)
    • 适用于任务量已知,相对耗时的长期任务
    • 注意这里就算线程执行完任务之后,线程任然会处于执行状态,并不会停止

newCachedThreadPool:创建一个可扩容的线程池
  • 其构造方法如下:
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

    • 核心线程数是 0,相当于全部是救急线程, 最大线程数是 29 个 1,全部都是救急线程(60s 后可以回收),可能会创建大量线程,从而导致 OOM
    • SynchronousQueue 作为阻塞队列,没有容量,对于每一个 take 的线程会阻塞直到有一个 put 的线程放入元素为止(类似一手交钱、一手交货)
    • 使用场景:适合任务数比较密集,但每个任务执行时间较短的情况
    • 整体来说:整个线程池的线程数会根据任务量不断增长,没有上限,当前任务执行完毕,空闲1分钟后就会释放线程

newSingleThreadExecutor:创建一个只有 1 个线程的单线程池
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

创建只有一个核心线程的线程池,任务队列使用的是无界队列

    • 保证所有任务按照指定顺序执行,线程数固定为 1,任务数多于 1 时会放入无界队列排队,任务执行完毕,这唯一的线程也不会被释放

对比:

  • 创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,线程池会新建一个线程,保证池的正常工作 (即使用保持一个线程进行工作)而如果我们自己定义一个线程进行任务,当遇到异常时就会停止,后面的任务就被抛弃了
  • Executors.newSingleThreadExecutor() 线程个数始终为 1,不能修改。FinalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法
    原因:父类不能直接调用子类中的方法,需要反射或者创建对象的方式,可以调用子类静态方法
  • Executors.newFixedThreadPool(1) 初始时为 1,可以修改。对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用 setCorePoolSize 等方法进行修改
public ScheduledThreadPoolExecutor(int corePoolSize) {
    // 最大线程数固定为 Integer.MAX_VALUE,保活时间 keepAliveTime 固定为 0
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          // 阻塞队列是 DelayedWorkQueue
          new DelayedWorkQueue());
}
newScheduledThreadPool

任务调度线程池 ScheduledThreadPoolExecutor 继承 ThreadPoolExecutor:

  • 使用内部类 ScheduledFutureTask 封装任务
  • 使用内部类 DelayedWorkQueue 作为线程池队列,这个队列也可看作误解队列,他是会自动扩容的
  • 重写 onShutdown 方法去处理 shutdown 后的任务
  • 提供 decorateTask 方法作为 ScheduledFutureTask 的修饰方法,以便开发者进行扩展

构造方法:Executors.newScheduledThreadPool(int corePoolSize)

public ScheduledThreadPoolExecutor(int corePoolSize) {
    // 最大线程数固定为 Integer.MAX_VALUE,保活时间 keepAliveTime 固定为 0
    super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
          // 阻塞队列是 DelayedWorkQueue
          new DelayedWorkQueue());
}
定时任务提交方法

  • ScheduledFuture<?> schedule(Runnable/Callable<V>, long delay, TimeUnit u):延迟执行任务
  • ScheduledFuture<?> scheduleAtFixedRate(Runnable/Callable<V>, long initialDelay, long period, TimeUnit unit)定时执行周期任务,不考虑执行的耗时,参数为初始延迟时间、间隔时间、单位(相当于起点到起点之间的定时)

如果,执行任务耗时>设置的时间间隔,那么每个任务的执行间隔时间就为执行任务时间

  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable/Callable<V>, long initialDelay, long delay, TimeUnit unit):定时执行周期任务,在启动线程后延时initialDelay这么长时间,即一次任务的结束到下一次任务的开始

基本使用:

  • 注意 延迟任务,但是出现异常并不会在控制台打印,也不会影响其他线程的执行
public static void main(String[] args){
    // 线程池大小为1时也是串行执行
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);
    // 添加两个任务,都在 1s 后同时执行
    executor.schedule(() -> {
    	System.out.println("任务1,执行时间:" + new Date());
        //int i = 1 / 0;
    	try { Thread.sleep(2000); } catch (InterruptedException e) { }
    }, 1000, TimeUnit.MILLISECONDS);
    
    executor.schedule(() -> {
    	System.out.println("任务2,执行时间:" + new Date());
    }, 1000, TimeUnit.MILLISECONDS);
}

  • 定时任务 scheduleAtFixedRate:一次任务的启动到下一次任务的启动之间只要大于等于间隔时间,抢占到 CPU 就会立即执行 ,需要注意的时要是执行任务耗时大于了定时周期那么就只能等到任务执行完后继续执行
public static void main(String[] args) {
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    System.out.println("start..." + new Date());
    
    pool.scheduleAtFixedRate(() -> {
        System.out.println("running..." + new Date());
        Thread.sleep(2000);
    }, 1, 1, TimeUnit.SECONDS);
}

/*start...Sat Apr 24 18:08:12 CST 2021
running...Sat Apr 24 18:08:13 CST 2021
running...Sat Apr 24 18:08:15 CST 2021
running...Sat Apr 24 18:08:17 CST 2021

  • 定时任务 scheduleWithFixedDelay:一次任务的结束到下一次任务的启动之间等于间隔时间,抢占到 CPU 就会立即执行,这个方法才是真正的设置两个任务之间的间隔
public static void main(String[] args){
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(3);
    System.out.println("start..." + new Date());
    
    pool.scheduleWithFixedDelay(() -> {
        System.out.println("running..." + new Date());
        Thread.sleep(2000);
    }, 1, 1, TimeUnit.SECONDS);
}
/*start...Sat Apr 24 18:11:41 CST 2021
running...Sat Apr 24 18:11:42 CST 2021
running...Sat Apr 24 18:11:45 CST 2021
running...Sat Apr 24 18:11:48 CST 2021
开发要求

阿里巴巴 Java 开发手册要求:

  • 线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
    • 使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题
    • 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者过度切换的问题
  • 线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式更加明确线程池的运行规则,规避资源耗尽的风险
    Executors 返回的线程池对象弊端如下:
    • FixedThreadPool 和 SingleThreadPool:请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM
    • CacheThreadPool 和 ScheduledThreadPool:允许创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,导致 OOM
创建多大容量的线程池合适?

  • 一般来说池中总线程数是核心池线程数量两倍,确保当核心池有线程停止时,核心池外有线程进入核心池
  • 过小会导致程序不能充分地利用系统资源、容易导致饥饿
  • 过大会导致更多的线程上下文切换,占用更多内存
    上下文切换:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态,任务从保存到再加载的过程就是一次上下文切换

线程池创建多少线程合适:

  • CPU 密集型任务 (N+1): 这种任务消耗的是 CPU 资源,可以将核心线程数设置为 N (CPU 核心数) + 1,比 CPU 核心数多出来的一个线程是为了防止线程发生缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 某个核心就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间
    CPU 密集型简单理解就是利用 CPU 计算能力的任务比如在内存中对大量数据进行分析
  • I/O 密集型任务: 这种系统 CPU 处于阻塞状态,用大部分的时间来处理 I/O 交互,远程RPC调用时,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用,因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N 或 CPU 核数/ (1-阻塞系数),阻塞系数在 0.8~0.9 之间
    IO 密集型就是涉及到网络读取,文件读取此类任务 ,特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上

经验公式:

线程数=核数*期望CPU利用率*总时间(CPU计算时间+等待时间)/CPU计算时间

public class ExcutorsTest {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(2, new ThreadFactory() {
           AtomicInteger t=new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                return new Thread(r,"线程--"+(t.getAndIncrement()+1));
            }
        });

        pool.execute(()->{
            System.out.println(Thread.currentThread().getName()+"1");
        });
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName()+"2");


        });
        pool.execute(()->{
            System.out.println(Thread.currentThread().getName()+"3");


        });

    }
}

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

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

相关文章

代码随想录 | 回溯算法总结

在代码随想录算法 | 回溯算法先导知识 | 题目分类&#xff0c;理论基础-CSDN博客中我们详细的介绍了回溯算法的理论知识&#xff0c;不同于教科书般的讲解&#xff0c;这里介绍的回溯法的效率&#xff0c;解决的问题以及模板都是在刷题的过程中非常实用&#xff01; 回溯是递归…

黑神话悟空|风灵月影 35项修改器下载

《黑神话&#xff1a;悟空》是由游戏科学公司制作的一款动作角色扮演游戏&#xff0c;于2024年8月20日正式发售。游戏改编自中国著名的神魔小说《西游记》&#xff0c;玩家在游戏中将扮演一位“天命人”&#xff0c;踏上一条充满危险与惊奇的西游之路。下面为带来这款游戏的修改…

AI人像换脸!Reactor插件本地部署方法(含报错解决及整合包)

​ Reactor插件是什么&#xff1f;有什么用&#xff1f; Reactor 是一个用于 Stable Diffusion 的换脸插件&#xff0c; 主要功能是实现图片中的精确换脸。它可以自动检测并替换图片中的多个面部&#xff0c;适用于多种场景&#xff0c;比如生成逼真的图像或者进行复杂的图片处…

InternVL多模态模型训练教程,流程图识别检测LLM-v1版本。检测流程图,输出基础图形bounding box

文章目录 项目介绍求一个star环境准备模型下载多模态大语言模型 (InternVL 2.0) 构造训练数据集单张图片&#xff1a;Grounding / Detection Data 开始训练 项目介绍 本篇文章主要是讲如何训练InternVL2模型&#xff0c;详细信息可以看我的Github repo&#xff0c;欢迎star&am…

ffplay源码分析(二)结构体VideoState

在多媒体的世界里&#xff0c;播放器是离用户最近的一环&#xff0c;它将数字编码的音频和视频数据转化为生动的视听体验。ffplay 播放器作为一款强大而备受关注的工具&#xff0c;其背后隐藏着一系列精妙的结构体&#xff0c;它们协同工作&#xff0c;共同完成了从数据读取、解…

Unity3D 遍历预制体

Unity3D 遍历预制体进行批量化处理。 遍历预制体 有时候&#xff0c;我们需要对一些预制体资源进行批量化处理&#xff0c;如果每一个预制体都手动处理&#xff0c;就会耗费很多时间精力&#xff0c;也容易出错。 我们可以写一个脚本遍历预制体&#xff0c;对预制体进行修改…

单HTML文件集成vue3+ElementPlus的使用

1、新建一个HTML文件 2、HTML文件引用vue3.js 3、引用elementplus.js和elementplus.css 4、Vue初始化ElementPlus 5、页面中可以使用ElementPlus啦 HTML文件例子如下&#xff1a; <html><head><meta charset"UTF-8"><script src"./js/vue…

NSTimer 引发的循环引用(内存泄漏)| NSTimer强引用

在iOS中使用NSTimer(定时器)不当会引发内存泄漏. NSTimer 有多种创建方式,具体可以看这位朋友的文章:https://blog.51cto.com/u_16099225/6716123 我这里主要讲使用NSTimer 会引发的内存泄漏情况以及解决方法: 内存泄漏出现的场景: VC A push 到VC B, VC B里启动了一个 NST…

Java基础之方法与数组

方法 在Java中&#xff0c; 方法的定义包括方法的修饰符、返回类型、方法名、参数列表和方法体。方法既能够模块化的组织代码(当代码规模比较复杂的时候)。也做到代码被重复使用&#xff08;一份代码可以在多个位置使用&#xff09;。Java中的方法类似与C语言中的函数&#xf…

Java SpringBoot实战教程:如何一步步构建保险业务管理与数据分析系统

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

LlamaIndex 实现 RAG(三)- 向量数据

RAG 中使用向量存储知识和文档数据&#xff0c;召回时通过语意进行搜索。文档转为向量是个非常消耗时的操作&#xff0c;不同 Embedding Model 参数不同&#xff0c;结果维度也不同&#xff0c;消耗的算力也不同。所以通常的做法都会在索引阶段&#xff08;Embedding&#xff0…

deeplab3-plus(中文翻译)

** Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation 文章目录 Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation1 Introduction2 Related Work3 Methods3.1 Encoder-Decoder with Atrous Convolution…

鸿蒙南向开发:测试框架xdevice核心组件

简介 xdevice是OpenHarmony中为测试框架的核心组件&#xff0c;提供用例执行所依赖的相关服务。 xdevice主要包括以下几个主要模块&#xff1a; command&#xff0c;用户与测试平台命令行交互模块&#xff0c;提供用户输入命令解析&#xff0c;命令处理。config&#xff0c;…

electron仿微信,高度还原,入门项目

效果展示 Electron仿写微信-效果展示 目前完成了一些基础的功能&#xff0c;还在持续开发中&#xff0c;后期会整理开源。 有些样式没有追求百分百还原&#xff0c;只是通过该项目&#xff0c;让大家了解一下Electron创建桌面应用&#xff0c;各种窗口的创建及销毁、事件传递机…

NLP从零开始------13.文本中阶序列处理之语言模型(1)

语言模型( language model) 用于计算一个文字序列的概率&#xff0c; 评估该序列作为一段文本出现在通用或者特定场景中的可能性。每个人的语言能力蕴涵了一个语言模型&#xff0c;当我们说出或写下一段话的时候&#xff0c;已经在不自觉地应用语言模型来帮助我们决定这段话中的…

viscode 自定义片段,快速生成自己的开发模板

设置 ---> 代码片段 2.选择新建全局代码片段文件 3.根据示例自定义配置代码片段 4.示例:vue prefix:内容--> 代表用于触发代码片段的内容 $1&#xff0c; $2 用于制表位,如 $1 代表生成后第一个输入的位置,$2代表第二个,不用自己移动鼠标 {// Place your snippets f…

Sac格式

本文章只作为自己学习时的用法&#xff0c;不通用&#xff0c;大家可不用参考。 sac格式 0.01000000 -1.569280 1.520640 -12345.00 -12345.009.459999 19.45000 -41.43000 10.46400 -12345.00-12345.00 -12345.00 -12…

SQL注入漏洞的基础知识

目录 SQL注入漏洞的定义和原理 SQL注入的类型和攻击方法 SQL注入的防御措施 示例代码 深入研究 SQL注入漏洞的常见攻击场景有哪些&#xff1f; 如何有效防范SQL注入攻击&#xff1f; SQL注入与跨站脚本攻击&#xff08;XSS&#xff09;之间有什么区别&#xff1f; 主要…

每日一练【最大连续1的个数 III】

一、题目描述 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 二、题目解析 本题同样是利用滑动窗口的解法。 首先进入窗口&#xff0c;如果是1&#xff0c;就直接让right&#xff0c;但是如果是…

【软考】树、森林和二叉树之间的相互转换

目录 1. 说明2. 树、森林转换为二叉树2.1 树转成二叉树2.1 森林转成二叉树 3. 二叉树转换为树和森林 1. 说明 1.树、森林和二叉树之间可以互相进行转换&#xff0c;即任何一个森林或一棵树可以对应表示为一棵叉树&#xff0c;而任何一棵二叉树也能对应到一个森林或一棵树上。 …