第十五章 Java多线程--线程池

news2024/10/12 22:01:04

目录

一、线程池基础概念

常见的线程池类型:

创建线程池的例子:

注意事项:

二、线程池使用场景

三、JDK自带的构建线程池的方式

1 newFixedThreadPool

2 newSingleThreadExecutor

3 newCachedThreadPool

4 newScheduleThreadPool

5 newWorkStealingPool


一、线程池基础概念

Java 中的线程池是一种管理执行线程的方式。使用线程池可以有效地管理和控制活动线程的数量,提高系统的响应性和可用性。Java 提供了几个类和接口来帮助创建和管理线程池,这些主要位于 java.util.concurrent 包中。

常见的线程池类型:

  • FixedThreadPool:固定大小的线程池,线程数量固定,可以复用线程。如果一个任务等待的时间超过了最大队列长度,那么这个线程池将会阻塞,直到有线程可用或者拒绝该任务。

  • CachedThreadPool:可缓存的线程池,线程数量不定,但空闲的线程会被终止以减少内存消耗。当有新的任务到达时,已终止的线程会被重新创建并执行任务。

  • SingleThreadExecutor:单个后台线程执行器,只包含一个线程的线程池。所有的任务会在单个线程中顺序执行。

  • ScheduledThreadPool:定时的线程池,支持调度执行任务。它允许延迟初始化线程,并且可以设置固定的周期来重复执行任务。

创建线程池的例子:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        // 创建一个固定大小为5的线程池
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);
        
        for (int i = 0; i < 10; i++) {
            final int taskId = i;
            // 提交任务给线程池
            fixedThreadPool.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println("正在处理任务 " + taskId);
                }
            });
        }
        
        // 关闭线程池
        fixedThreadPool.shutdown();
        // 等待所有任务完成
        while (!fixedThreadPool.isTerminated()) {
        }
        System.out.println("所有任务已完成");
    }
}

注意事项:

  • 当不再需要线程池时,应该调用 shutdown()shutdownNow() 方法来关闭线程池。shutdown() 允许已经提交的任务被执行完毕后关闭线程池,而 shutdownNow() 尝试立即停止所有正在执行的任务,并返回尚未执行的任务列表。

  • 要合理设置线程池中的线程数,过多的线程可能导致系统资源耗尽,过少则可能无法充分利用系统资源。

  • 在设计任务执行策略时,要考虑线程池的饱和度和任务排队机制,以避免因为任务积压而导致性能下降或服务不可用。

二、线程池使用场景

为什么要使用线程池

在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。

比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可以了。

比如做业务操作时,需要发送短信或者是发送邮件,这种操作也可以基于异步的方式完成,这种异步的方式,其实就是再构建一个线程去执行。

但是,如果每次异步操作或者多线程操作都需要新创建一个线程,使用完毕后,线程再被销毁,这样的话,对系统造成一些额外的开销。在处理过程中到底由多线程处理了多少个任务,以及每个线程的开销无法统计和管理。

所以咱们需要一个线程池机制来管理这些内容。线程池的概念和连接池类似,都是在一个Java的集合中存储大量的线程对象,每次需要执行异步操作或者多线程操作时,不需要重新创建线程,直接从集合中拿到线程对象直接执行方法就可以了。

JDK中就提供了线程池的类。

在线程池构建初期,可以将任务提交到线程池中。会根据一定的机制来异步执行这个任务。

  • 可能任务直接被执行

  • 任务可以暂时被存储起来了。等到有空闲线程再来处理。

  • 任务也可能被拒绝,无法被执行。

JDK提供的线程池中记录了每个线程处理了多少个任务,以及整个线程池处理了多少个任务。同时还可以针对任务执行前后做一些勾子函数的实现。可以在任务执行前后做一些日志信息,这样可以多记录信息方便后面统计线程池执行任务时的一些内容参数等等……

三、JDK自带的构建线程池的方式

JDK中基于Executors提供了很多种线程池

1 newFixedThreadPool

这个线程池的特别是线程数是固定的。

在Executors中第一个方法就是构建newFixedThreadPool

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
            0L, TimeUnit.MILLISECONDS,
            new LinkedBlockingQueue<Runnable>());
}

构建时,需要给newFixedThreadPool方法提供一个nThreads的属性,而这个属性其实就是当前线程池中线程的个数。当前线程池的本质其实就是使用ThreadPoolExecutor。

构建好当前线程池后,线程个数已经固定好**(线程是懒加载,在构建之初,线程并没有构建出来,而是随着任务的提交才会将线程在线程池中构建出来)**。如果线程没构建,线程会待着任务执行被创建和执行。如果线程都已经构建好了,此时任务会被放到LinkedBlockingQueue无界队列中存放,等待线程从LinkedBlockingQueue中去take出任务,然后执行。

测试功能效果

public static void main(String[] args) throws Exception {
    ExecutorService threadPool = Executors.newFixedThreadPool(3);
    threadPool.execute(() -> {
        System.out.println("1号任务:" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
        System.out.println("2号任务:" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    threadPool.execute(() -> {
        System.out.println("3号任务:" + Thread.currentThread().getName() + System.currentTimeMillis());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
}

2 newSingleThreadExecutor

这个线程池看名字就知道是单例线程池,线程池中只有一个工作线程在处理任务

如果业务涉及到顺序消费,可以采用newSingleThreadExecutor

// 当前这里就是构建单例线程池的方式
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
    // 在内部依然是构建了ThreadPoolExecutor,设置的线程个数为1
    // 当任务投递过来后,第一个任务会被工作线程处理,后续的任务会被扔到阻塞队列中
    // 投递到阻塞队列中任务的顺序,就是工作线程处理的顺序
    // 当前这种线程池可以用作顺序处理的一些业务中
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

static class FinalizableDelegatedExecutorService extends DelegatedExecutorService {
    // 线程池的使用没有区别,跟正常的ThreadPoolExecutor没区别
    FinalizableDelegatedExecutorService(ExecutorService executor) {
        super(executor);
    }
    // finalize是当前对象被GC干掉之前要执行的方法
    // 当前FinalizableDelegatedExecutorService的目的是为了在当前线程池被GC回收之前
    // 可以执行shutdown,shutdown方法是将当前线程池停止,并且干掉工作线程
    // 但是不能基于这种方式保证线程池一定会执行shutdown
    // finalize在执行时,是守护线程,这种线程无法保证一定可以执行完毕。
    // 在使用线程池时,如果线程池是基于一个业务构建的,在使用完毕之后,一定要手动执行shutdown,
    // 否则会造成JVM中一堆线程protected void finalize() {
        super.shutdown();
    }
}

测试单例线程池效果:

public static void main(String[] args) throws Exception {
    ExecutorService threadPool = Executors.newSingleThreadExecutor();

    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + "," + "111");
    });
    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + "," + "222");
    });
    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + "," + "333");
    });
    threadPool.execute(() -> {
        System.out.println(Thread.currentThread().getName() + "," + "444");
    });
}

测试线程池使用完毕后,不执行shutdown的后果:

如果是局部变量仅限当前线程池使用的线程池,在使用完毕之后要记得执行shutdown,避免线程无法结束

 

如果是全局的线程池,很多业务都会到,使用完毕后不要shutdown,因为其他业务也要执行当前线程池

 

static ExecutorService threadPool = Executors.newFixedThreadPool(200);

public static void main(String[] args) throws Exception {
    newThreadPool();
    System.gc();
    Thread.sleep(5000);
    System.out.println("线程池被回收了!!");
    System.in.read();
}

private static void newThreadPool(){
    for (int i = 0; i < 200; i++) {
        final int a = i;
        threadPool.execute(() -> {
            System.out.println(a);
        });
    }
    threadPool.shutdown();
    for (int i = 0; i < 200; i++) {
        final int a = i;
        threadPool.execute(() -> {
            System.out.println(a);
        });
    }
}

3 newCachedThreadPool

看名字好像是一个缓存的线程池,查看一下构建的方式

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

当第一次提交任务到线程池时,会直接构建一个工作线程

这个工作线程带执行完后,60秒没有任务可以执行后,会结束

如果在等待60秒期间有任务进来,他会再次拿到这个任务去执行

如果后续提升任务时,没有线程是空闲的,那么就构建工作线程去执行。

最大的一个特点,任务只要提交到当前的newCachedThreadPool中,就必然有工作线程可以处理

代码测试效果

public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 1; i <= 200; i++) {
        final int j = i;
        executorService.execute(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":" + j);
        });
    }
}

4 newScheduleThreadPool

首先看到名字就可以猜到当前线程池是一个定时任务的线程池,而这个线程池就是可以以一定周期去执行一个任务,或者是延迟多久执行一个任务一次

查看一下如何构建的。

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

基于这个方法可以看到,构建的是ScheduledThreadPoolExecutor线程池

public class ScheduledThreadPoolExecutor extends ThreadPoolExecutor{
        //....
}

所以本质上还是正常线程池,只不过在原来的线程池基础上实现了定时任务的功能

原理是基于DelayQueue实现的延迟执行。周期性执行是任务执行完毕后,再次扔回到阻塞队列。

代码查看使用的方式和效果

public static void main(String[] args) throws Exception {
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);

    // 正常执行
    //        pool.execute(() -> {
    //            System.out.println(Thread.currentThread().getName() + ":1");
    //        });
    // 延迟执行,执行当前任务延迟5s后再执行
    //        pool.schedule(() -> {
    //            System.out.println(Thread.currentThread().getName() + ":2");
    //        },5,TimeUnit.SECONDS);
    // 周期执行,当前任务第一次延迟5s执行,然后每3s执行一次
    // 这个方法在计算下次执行时间时,是从任务刚刚开始时就计算。
    //        pool.scheduleAtFixedRate(() -> {
    //            try {
    //                Thread.sleep(3000);
    //            } catch (InterruptedException e) {
    //                e.printStackTrace();
    //            }
    //            System.out.println(System.currentTimeMillis() + ":3");
    //        },2,1,TimeUnit.SECONDS);
    // 周期执行,当前任务第一次延迟5s执行,然后每3s执行一次
    // 这个方法在计算下次执行时间时,会等待任务结束后,再计算时间
    pool.scheduleWithFixedDelay(() -> {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(System.currentTimeMillis() + ":3");
    },2,1,TimeUnit.SECONDS);
}

至于Executors提供的newSingleThreadScheduledExecutor单例的定时任务线程池就不说了。

一个线程的线程池可以延迟或者以一定的周期执行一个任务。

5 newWorkStealingPool

当前JDK提供构建线程池的方式newWorkStealingPool和之前的线程池很非常大的区别

之前定长,单例,缓存,定时任务都基于ThreadPoolExecutor去实现的。

newWorkStealingPool是基于ForkJoinPool构建出来的

ThreadPoolExecutor的核心点

在ThreadPoolExecutor中只有一个阻塞队列存放当前任务

ForkJoinPool的核心特点:

ForkJoinPool从名字上就能看出一些东西。当有一个特别大的任务时,如果采用上述方式,这个大任务只能会某一个线程去执行。ForkJoin第一个特点是可以将一个大任务拆分成多个小任务,放到当前线程的阻塞队列中。其他的空闲线程就可以去处理有任务的线程的阻塞队列中的任务

来一个比较大的数组,里面存满值,计算总和

单线程处理一个任务:

/** 非常大的数组 */static int[] nums = new int[1_000_000_000];
// 填充值static{
    for (int i = 0; i < nums.length; i++) {
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
    // ===================单线程累加10亿数据================================
    System.out.println("单线程计算数组总和!");
    long start = System.nanoTime();
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    long end = System.nanoTime();
    System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (end  - start));
}

多线程分而治之的方式处理:

/** 非常大的数组 */
static int[] nums = new int[1_000_000_000];
// 填充值
static{
    for (int i = 0; i < nums.length; i++) {
        nums[i] = (int) ((Math.random()) * 1000);
    }
}
public static void main(String[] args) {
    // ===================单线程累加10亿数据================================
    System.out.println("单线程计算数组总和!");
    long start = System.nanoTime();
    int sum = 0;
    for (int num : nums) {
        sum += num;
    }
    long end = System.nanoTime();
    System.out.println("单线程运算结果为:" + sum + ",计算时间为:" + (end  - start));

    // ===================多线程分而治之累加10亿数据================================
    // 在使用forkJoinPool时,不推荐使用Runnable和Callable
    // 可以使用提供的另外两种任务的描述方式
    // Runnable(没有返回结果) ->   RecursiveAction
    // Callable(有返回结果)   ->   RecursiveTask
    ForkJoinPool forkJoinPool = (ForkJoinPool) Executors.newWorkStealingPool();
    System.out.println("分而治之计算数组总和!");
    long forkJoinStart = System.nanoTime();
    ForkJoinTask<Integer> task = forkJoinPool.submit(new SumRecursiveTask(0, nums.length - 1));
    Integer result = task.join();
    long forkJoinEnd = System.nanoTime();
    System.out.println("分而治之运算结果为:" + result + ",计算时间为:" + (forkJoinEnd  - forkJoinStart));
}

private static class SumRecursiveTask extends RecursiveTask<Integer>{
    /** 指定一个线程处理哪个位置的数据 */
    private int start,end;
    private final int MAX_STRIDE = 100_000_000;
    //  200_000_000: 147964900
    //  100_000_000: 145942100
    public SumRecursiveTask(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Overrideprotected Integer compute() {
        // 在这个方法中,需要设置好任务拆分的逻辑以及聚合的逻辑
        int sum = 0;
        int stride = end - start;
        if(stride <= MAX_STRIDE){
            // 可以处理任务
            for (int i = start; i <= end; i++) {
                sum += nums[i];
            }
        }else{
            // 将任务拆分,分而治之。
            int middle = (start + end) / 2;
            // 声明为2个任务
            SumRecursiveTask left = new SumRecursiveTask(start, middle);
            SumRecursiveTask right = new SumRecursiveTask(middle + 1, end);
            // 分别执行两个任务
            left.fork();
            right.fork();
            // 等待结果,并且获取sum
            sum = left.join() + right.join();
        }
        return sum;
    }
}

最终可以发现,这种累加的操作中,采用分而治之的方式效率提升了2倍多。

但是也不是所有任务都能拆分提升效率,首先任务得大,耗时要长。

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

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

相关文章

震撼!一句话就让 AI 帮你做 UI 测试,多模态测试智能体 AUITestAgent 横空出世!

美团到店研发平台携手复旦大学周扬帆教授团队&#xff0c;共同开发了智能化终端测试工具AUITestAgent。该工具是第一个能够基于自然语言测试用例&#xff0c;自动化完成终端UI测试驱动、校验全流程的智能化测试工具。仅需输入自然语言形式的测试需求&#xff0c;AUITestAgent通…

福建谷器参加泉州市中小企业数字化转型试点工作启动会

为进一步加快推动试点城市工作,10月9日,泉州市产业数字化转型工作现场会暨2024年中小企业数字化转型试点工作启动会成功举办。出席本次会议的有福建省工业和信息化厅副厅长许永西、泉州市人民政府副市长雷连鸣等领导,及来自国家工业信息安全发展研究中心、中国工业互联网研究院…

newlibc memcpy 存在问题

背景 sdk 中发现 memcpy 函数没有达到预期&#xff0c;执行后&#xff0c;目的地址与源地址中的内容不一致。 复现方法 通过单步调试 memcpy 汇编代码&#xff0c;发现使用了 ldrh 指令&#xff0c;该指令在对 uncacheable memory 同时该 memory 非对齐的情况下&#xff0c;…

高性能计算平台(HPC)如何选型

选型高性能计算平台&#xff08;HPC&#xff09;非常复杂&#xff0c;需要考针对行业的痛点等多个因素进行考虑&#xff0c;来确保平台系统能满足特定行业和应用的需求。下面为大家列举了几个方面&#xff0c;大家可以参考。 1.计算需求 首先需要了解你需要处理的数据类型、计算…

如何给照片加文字?几个方法帮助你给照片轻松填字

如何给照片加文字&#xff1f;几个方法帮助你给照片轻松填字 给照片添加文字是常见的图片处理需求&#xff0c;尤其是在社交媒体、海报设计和个人相册制作中。以下是5款常用软件&#xff0c;它们能够帮助你轻松在照片上添加文字&#xff0c;满足不同层次的用户需求&#xff0c…

os镜像包一键安装

5、解压后点击运行旗胜PE维护系统。 6、插入U盘识别到U盘后点那个蓝色的制作启动U盘就可以了&#xff0c;设置用默认的就行不用改。 7、正在制作启动U盘中大约5分钟左右。 8、出现这个页面就制作完成了&#xff0c;可以关掉这个软件了 9、打开启动U盘就可以看到里面有os文件夹跟…

Python酷库之旅-第三方库Pandas(145)

目录 一、用法精讲 656、pandas.Timestamp.resolution属性 656-1、语法 656-2、参数 656-3、功能 656-4、返回值 656-5、说明 656-6、用法 656-6-1、数据准备 656-6-2、代码示例 656-6-3、结果输出 657、pandas.Timestamp.second属性 657-1、语法 657-2、参数 6…

Unity XR PICO 手势交互 Demo APK

效果展示 用手抓取物体&#xff0c;调整物体位置和大小等 亲测pico4 企业版可用&#xff0c; 其他设备待测试 下载链接&#xff1a; 我标记的不收费 https://download.csdn.net/download/qq_35030499/89879333

可观察性的三大支柱:统一日志、指标和跟踪

作者&#xff1a;来自 Elastic Elastic Observability Team 了解遥测信号&#xff0c;以便做出更好的决策、提高性能并增强客户体验。 多年来&#xff0c;遥测信号已经发生了重大变化 —— 如果你眨眼&#xff0c;你可能会错过它。事实上&#xff0c;关于可观察性的许多常识都…

DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中?

[ 知识是人生的灯塔&#xff0c;只有不断学习&#xff0c;才能照亮前行的道路 ] 原文链接&#xff1a;DBA | 如何将 .mdf 与 .ldf 的数据库文件导入到SQL Server 数据库中? 如何将 (.mdf) 和 (.ldf) 的SQL Server 数据库文件导入到当前数据库中? Step 1.登录到 Sql Server 服…

“云计算+高职”:VR虚拟仿真实训室的发展前景

随着科技的飞速进步&#xff0c;云计算与虚拟现实&#xff08;VR&#xff09;技术的结合正在深刻改变着教育领域&#xff0c;尤其是在高等职业教育中&#xff0c;这一融合为实训教学带来了革命性的变革。VR虚拟仿真实训室作为这一变革的前沿阵地&#xff0c;正展现出广阔的发展…

PCL点云处理之求法向量

求法向量干什么&#xff1f;将点渲染成面 1、一个点垂直于一个曲线的切线叫法线 2、在点云中取一块区域&#xff0c;用最小二乘将区域中的点云拟合成一个面&#xff08;贴合在曲面上的一个切面&#xff09;在相近的区域计算出n个这样的面&#xff0c;用这个面求出法向量&#…

LibreOffice SDK是LibreOffice软件的开发工具包

LibreOffice SDK是LibreOffice软件的开发工具包&#xff0c;它提供了一系列工具和库&#xff0c;使得开发者可以基于LibreOffice进行扩展或开发新的应用程序。以下是对LibreOffice SDK的详细介绍&#xff1a; 一、下载与安装 下载地址&#xff1a; 可以在LibreOffice的官方网站…

警惕!.rmallox勒索病毒:加密你的文件,勒索你的钱包

导言 在数字化时代&#xff0c;网络安全威胁层出不穷&#xff0c;其中勒索病毒已成为企业和个人用户面临的一大挑战。特别是.rmallox勒索病毒&#xff0c;以其复杂的加密算法和广泛的传播方式&#xff0c;给数据安全带来了严重威胁。本文91数据恢复将详细介绍.rmallox勒索病毒…

SCSI-8.UFS_RPMB

SCSI-8.UFS_RPMB RPMB 介绍 **RPMB&#xff08;Replay Protected Memory Block&#xff09;**是一种基于硬件的安全存储区域&#xff0c;其结构设计旨在确保数据的机密性、完整性以及防重放攻击的能力。RPMB通常嵌入在eMMC、UFS等存储设备中&#xff0c;由专用硬件电路管理和…

TCN-Transformer时间序列预测(多输入单预测)——基于Pytorch框架

1 数据集介绍 我们使用的数据集包含以下几个重要的属性&#xff1a; date&#xff08;日期&#xff09; open&#xff08;开盘价&#xff09; high&#xff08;最高价&#xff09; low&#xff08;最低价&#xff09; close&#xff08;收盘价&#xff09; pre_close&…

搜索如何加速你迈向 “AI 优先” 的步伐

作者&#xff1a;来自 Elastic Hayley Sutherland 人工智能与搜索的结合使企业智能达到了新的水平&#xff0c;自然语言处理 (NLP)、基于机器学习 (ML) 的相关性、向量/语义搜索和大型语言模型 (LLMs) 等技术帮助组织最终释放未分析数据的价值。组织需要搜索和知识发现技术来发…

uniapp 整合 OpenLayer3 - 全图、切换底图、导航、定位

一、全图 主要代码&#xff1a; // 获取当前可见视图范围 //console.log(this.map.getView().calculateExtent()); // 设置中心点 //this.map.getView().setCenter(transform([125.33,43.90], EPSG:4326, EPSG:3857)); // 设置层级 //this.map.getView().setZoom(10);// 中心…

【ProtoBuf】ProtoBuf基础与安装

本篇文章介绍 C 使用方向 文章目录 ProtoBuf简介ProtoBuf安装WindowsLinux ProtoBuf简介 ProtoBuf(全称为 Protocol Buffer)是一种序列化结构数据的方法 序列化是将对象转换为可存储的或传输的格式的过程&#xff0c;通常用于数据交换或持久化存储。我们在C/Java中编写的类不…

JAVA-数据结构-排序

1.直接插入排序 1.原理&#xff1a;和玩扑克牌一样&#xff0c;从左边第二个牌开始&#xff0c;选中这个&#xff0c;和前面的所有牌比较&#xff0c;插在合适的位置 public static void insertsort(int[] arr){//直接插入排序for (int i 1; i < arr.length; i) {//此循环…