首页 > 其他分享 >自定义线程池的使用

自定义线程池的使用

时间:2024-10-15 15:22:32浏览次数:3  
标签:end 自定义 使用 线程 executor finalEnd 报错 final

现在有一个使用多线程进行调优的案例:模拟使用多线程并发处理数据。

1.自定义一个线程池

//自定义一个线程池
class ThreadPoolManger {
    private final ThreadPoolExecutor executor;
    private final AtomicBoolean isShutdown;

    /**
     * 构造方法,用于初始化线程池。
     *
     * @param corePoolSize    核心线程池的大小
     * @param maximumPoolSize 最大线程池的大小
     * @param keepAliveTime   非核心线程空闲存活时间
     * @param unit            存活时间单位
     * @param queueCapacity   任务队列容量
     */
    public ThreadPoolManger(int corePoolSize, int maximumPoolSize, long keepAliveTime,
                            TimeUnit unit, int queueCapacity) {
        //防止参数为负数
        if (corePoolSize <= 0 || maximumPoolSize <= 0 || queueCapacity < 0) {
            throw new IllegalArgumentException("Pool size and capacity must be positive.");
        }

        executor = new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                keepAliveTime,
                unit,
                new LinkedBlockingQueue<>(queueCapacity)
        );
        //初始线程池关闭状态为false
        this.isShutdown = new AtomicBoolean(false);
        configThreadPool();
    }

    /**
     * 配置线程池的线程工厂和拒绝策略。
     */
    private void configThreadPool() {
        //设置自定义的线程工厂
        executor.setThreadFactory(new ThreadFactory() {
            @Override
            //这个参数r,是该线程要执行的任务
            public Thread newThread(Runnable r) {
                Thread thread = new Thread(r);
                thread.setName("CustomThreadPool" + thread.getId());
                thread.setDaemon(false);//是否设置为守护线程
                thread.setPriority(Thread.NORM_PRIORITY);
                return thread;
            }
        });
        //自定义拒绝策略
        executor.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            @Override
            /**
             * Runnable r:被拒绝的任务。
             * ThreadPoolExecutor executor:调用执行任务的线程池。
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                System.err.println("任务 " + r.toString() + " 被拒绝, 线程池 " + executor.toString() + " 已满");
            }
        });
    }

    //把任务提交给线程池
    public void submit(Runnable task) {
        if (isShutdown.get()) {
            throw new IllegalStateException("ThreadPool is shut down");
        }
        executor.execute(task); // 使用execute方法提交任务
    }

    //关闭线程池
    public void shutdown() {
        //CAS操作确保线程池只被关闭一次
        //如果false,说明有其他线程竞争关闭了
        if (isShutdown.compareAndSet(false, true)) {
            executor.shutdown();
            try {
                // 等待线程池关闭,这段时间留给其他线程执行完任务,最多等待30秒
                if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
                    // 如果超时,则尝试立即关闭
                    executor.shutdownNow();
                }
            } catch (InterruptedException e) {
                // 如果等待时被中断,重新设置线程中断标志,并尝试立即关闭线程池
                Thread.currentThread().interrupt();
                executor.shutdownNow();
            }
        }
    }

}

自定义线程池包含7个参数,这里我们全部自定义。

2.使用线程池并发处理数据

然后在main方法中,使用线程池中的线程,达到目的效果。

class ThreadDemo {

    //模拟项目中多线程的使用案例:并发处理数据
    public static void main(String[] args) {

        //假设这个集合就是我们要处理的数据集合,假设有10000条数据
        // 假设这是我们要处理的10000条数据
        List<Integer> data = new ArrayList<>();
        for (int i = 1; i <= 10000; i++) {
            data.add(i);
        }

        ThreadPoolManger threadPool = new ThreadPoolManger(
                5,10,20,TimeUnit.SECONDS,10
        );

        // 计算每个线程处理的数据量
        int totalData = data.size();
        int chunkSize = totalData / 10; // 每个线程处理1000条数据

        // 提交任务
        for (int i = 0; i < 10; i++) {
            int start = i * chunkSize;
            int end = start + chunkSize;

            // 确保最后一个线程处理所有剩余数据
            if (i == 9) {
                end = totalData; // 处理最后一部分数据
            }

            int finalEnd = end;
            threadPool.submit(new Runnable() {
                List<Integer> subList = data.subList(start, finalEnd);
                @Override
                public void run() {
                    for (Integer integer : subList) {
                        System.out.println("处理数据:"+integer);

                    }
                }
            });
        }
        // 关闭线程池
        threadPool.shutdown();
    }
}

3.碰到的问题

3.1

注意,这里我说两个我当时觉得奇怪的点:

两个问题:第一个,为什么这个end报错,start不报错

第二个问题:为什么这样把end复制一份,也没有加final修饰,finalEnd就不报错了

gpt的回答:

第一个问题:为什么第一段代码中 end 报错,而 start 不报错?

在Java中,匿名内部类(如 Runnable)可以访问外部类的变量,但这些变量必须是final或者是effectively final(即在赋值后没有被修改)。在第一段代码中,end 是一个循环变量,在每次循环迭代中都会被重新赋值。因此,当匿名内部类尝试使用 end 时,编译器无法确认 end 是 final 或 effectively final,所以会报错。

start 由于是局部变量,且在每次迭代中重新赋值,所以它也不能被访问,实际上它也会报错。但这里你可能没有注意到它的作用域问题,两个变量都是存在类似的问题。

第二个问题:为什么第二段代码中这样把 end 复制一份,也没有加 final 修饰,finalEnd 就不报错了?

在第二段代码中,finalEnd 是在 end 的基础上创建的一个新的局部变量。Java 的编译器能够确定 finalEnd 的值在定义后不会被修改,因此它被视为 effectively final。

当你将 end 的值赋给 finalEnd 时,finalEnd 不再依赖于外部变量 end 的生命周期。由于 finalEnd 的值在定义后不再改变,所以它符合可以在匿名内部类中使用的条件。

总结

  1. 第一个问题end 在循环中会被重新赋值,导致编译器认为它不是 final 或 effectively final,因此报错。
  2. 第二个问题finalEnd 是新的局部变量,且在赋值后不再修改,所以它符合匿名内部类的使用条件。

注意:如果把这段代码删掉,end变量没有再次赋值,就不会报错。

            if (i == 9) {
                end = totalData; // 处理最后一部分数据
            }

3.2

问题2,volatile关键字修饰的变量

volatile static int count = 0;

Java 中,count++ 这个操作实际上是由多个步骤组成的:读取当前值、加一、再写回。这些步骤在多线程环境中是非原子的,意味着两个线程可能会同时读取到相同的值,然后都加一并写回,最终只会增加一次计数,导致丢失更新。


但是,每个线程对于自己要处理的subList集合是不会出现数据丢失。因为每个线程都是处理自己的一段subList,没有像这个count一样,出现多线程竞争同一个变量的情况。

标签:end,自定义,使用,线程,executor,finalEnd,报错,final
From: https://blog.csdn.net/qq_64064246/article/details/142901501

相关文章

  • Linux 基础指令的使用
    1.mkdir创建文件夹在当前用户的桌面创建一个自己名字的文件夹cd~/桌面mkdirwanghuanls2.touch创建文件在自己名字目录下,新建一个1.c和2.c文件,新建一个test目录cdwanghuantouch1.ctouch2.cmkdirtest3.mv移动文件将1.c文件移到test目录下mv1.......
  • 关于最新版本mysql9,使用Kettle连接Mysql 9 报错,驱动问题
    使用kettle连接mysql时,报“Driverclassorg.gjt.mm.mysql.Drivercouldnotbefound”错,没有需要的connector包,在Mysql官网下载了最新的connector的jar包,并将其放在如下图的Kettle所示的目录中:重试发现仍然连接失败。这时候对比了网上大家的解决方法,发现我所下载的最新conne......
  • minio client 使用
    一、下载minioclientwgethttp://dl.minio.org.cn/client/mc/release/linux-amd64/mcchmod+xmc 二、添加server./mcconfighostaddminio-serverhttps://xxx.xxx.xxx.xxx:9100access-keysecret-key--apis3v4Added`minio-server`successfully../mcconfi......
  • 上手protobuf:一文掌握protobuf的使用
    简介在上一篇文章中,我们已经了解了protobuf是什么,还有proto文件该如何编写在本文中,将着重讲讲protobuf该怎么用Protobuf使用教程Protobuf是一种高效数据序列化协议,可支持多种编程语言,不同编程语言使用方法或略有差异、或有多种方法,以下主要介绍大致的、通用的使用方法安装编......
  • Exchange2016服务器使用到的9个端口(重要)
    Exchange2016服务器使用到的9个端口(重要)端口名称及用途:443:客户端连接CAS服务器(web,Outlook)80:有时候通过80端口访问owa,会进行重定向到443IMAP协议端口:     143:非加密端口     993:加密端口pop3协议端口:     110:非加密端口     995:加......
  • Metricbeat入门与使用
    Metricbeat简介Metricbeat是ElasticStack中的一个轻量级数据采集器,专门用于监测和收集系统及服务的指标数据。它能够实时地从各类来源获取性能数据,并将其发送到Elasticsearch或Logstash,以便进行分析、存储和可视化。主要特点:多源数据采集:支持从操作系统、容器(如Dock......
  • 在Vue3中使用vuex
    Vuex简介Vuex是Vue.js官方的状态管理库,帮助我们在中大型应用中集中管理组件间的共享状态。它通过state、getters、mutations和actions实现响应式数据管理Vuex核心概念State:全局状态,存储应用的核心数据。Getters:类似于组件中的计算属性,用于从state中派生出......
  • 多线程批量插入数据
    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档文章目录前言一、多线程使用背景二、代码实现1.单线程插入2.多线程插入总结前言在面试过程中我们经常会被问到多线程的问题。但是在实际工作过程中可能使用的场景不是特别多,在这边给大家提供一个多......
  • OpenCV高级图形用户界面(11)检查是否有键盘事件发生而不阻塞当前线程函数pollKey()的
    操作系统:ubuntu22.04OpenCV版本:OpenCV4.9IDE:VisualStudioCode编程语言:C++11算法描述轮询已按下的键。函数pollKey无等待地轮询键盘事件。它返回已按下的键的代码或如果没有键自上次调用以来被按下则返回-1。若要等待按键被按下,请使用waitKey。注意waitKey......
  • OpenCV高级图形用户界面(8)在指定的窗口中显示一幅图像函数imshow()的使用
    操作系统:ubuntu22.04OpenCV版本:OpenCV4.9IDE:VisualStudioCode编程语言:C++11算法描述在指定的窗口中显示一幅图像。函数imshow在指定的窗口中显示一幅图像。如果窗口是以cv::WINDOW_AUTOSIZE标志创建的,图像将以原始大小显示,不过仍然受限于屏幕分辨率。否则,图像......