• Java 线程池手动创建示例及自我理解的解读 ThreadFactory手动创建示例


    构建函数的参数说明

    JAVA 创建线程池七个参数作用及学习总结

    手动创建示例

    依旧是在 main 方法中做示例

    1.全参构造

    定义如下:
    注意这个是在方法外定义 为了多个方法使用同一个线程池以免浪费,由于我是在 main中做测试故需要定义为 static,一般使用时如果放在 spring InitializingBean 或其他初始话动作中的话 不必添加 static,如果在静态代码块中初始化则需要 static

    private static ThreadPoolExecutor threadPoolExecutor = null;
    
    • 1

    main 中做初始化以及使用

    threadPoolExecutor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500),  Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
                    
    threadPoolExecutor.submit(new BusinessThread(5));
    threadPoolExecutor.shutdown();
    
    • 1
    • 2
    • 3
    • 4

    简单解读
    这里 第一行的第一个构建参数即为核心线程数(这里我设置的10),即使空闲不在使用中也不会销毁,除非设置了核心线程超时时间的参数 即allowCoreThreadTimeOut 核心线程数即这个线程池的最小线程数。

    第一行第二个构建参数,即最大线程数 当核心线程数中的所有线程没有空闲时,再提交任务时会进入工作队列等待,当工作队列也满了时,会再次创建一个新的线程,并从工作队列的首部取出一个任务交给新创建的线程去执行,新来的任务则进入工作队列,插入队尾(相当于排队等待),但通过这样的方式创建的线程是存在上限的,最大线程数就是这个上限。当最大线程数也上限了怎么办?继续往下看,JDK 也给了相应的几种解决策略供使用。

    第一行第三个构建参数,第四个构建参数,即空闲线程等待时间,等待时间的单位,这里空闲线程指的并不是核心线程,而是上面通过最大线程数的方式,由于工作队列以及核心线程数均满时额外创建的线程,当这些线程空闲下来时,并等待到了指定的时间时依旧没有被使用,那么这些线程会被销毁。

    第一行第五个构建参数,即 工作队列的类型以及队列的大小,目前提供四种工作队列,这里我简单说,详细见最上方参数链接

    ArrayBlockingQueue
    有界阻塞队列,先进先出,新任务会放到队尾就是最后。核心线程没有空闲时,提交新任务则会放入队列的队尾,等待。
    如果队列已满,则创建一个新线程,如果线程数量已经达到maxPoolSize,则会执行拒绝策略。

    LinkedBlockingQuene
    无界阻塞队列(其实最大容量为Interger.MAX),先进先出。由于该队列的近似无界性(即没有上限),核心线程没有空闲时提交新任务会一直存入该队列,而不去创建新线程直到达到最大线程数,因此使用该工作队列时,参数 maxPoolSize 其实无用。

    SynchronousQuene
    不缓存任务的阻塞队列,生产者放入一个任务必须等到消费者取出这个任务。
    也就是说新任务进来时不会缓存,直接执行该任务,如果没有可用线程则创建新线程,如果线程数量达到 maxPoolSize,则执行拒绝策略。

    PriorityBlockingQueue
    具有优先级的无界阻塞队列,优先级通过参数Comparator实现。

    第一行第六个构建参数,即构建新线程的方式,一般使用默认的 factory(工厂)制造线程即可,一般是对当前线程做一些命名以及是否守护线程的属性赋值。如果有自己的需求可以参考源码的方式创建一个简单的 ThreadFactory ,下面一会儿会有一些例子提供参考。这里我使用的JDK 里面提供的默认创建方式,一般也够用了。

    第一行第七个构建参数,即拒绝策略,上面队列中一再说明了,当核心已满,队列已满,最大线程数也满的情况下会执行的拒绝策略,简单来说就是当定义的线程池已经没有能力处理新的任务时,再有新的任务提交时该怎么去处理,JDK 同样提供了四种拒绝策略:

    CallerRunsPolicy
    在调用者线程中直接执行被拒绝任务的run方法,除非线程池已经shutdown,则直接抛弃任务
    这里调用者指的是提交任务的线程,即执行提交任务代码当时所在的线程。

    AbortPolicy
    丢弃,并抛出RejectedExecutionException异常,一般提供的已有线程池大多都会选择这一种

    DiscardPolicy
    直接丢弃,什么都不做

    DiscardOldestPolicy
    抛弃进入队列最早的任务,然后尝试把这次拒绝的任务加入队列

    第二行
    提交一个线程任务,这里 new 的类为自定义的类,并使用了改类的某一个构造函数,该类继承 thread,或者 实现 runnable ,提交到线程池种会执行 重写的 run 方法的内容。

    第三行
    关闭当前线程池,会等待已有任务的执行完成,但是新的任务无法再提交。

    其它构造方法

    在这里插入图片描述
    这里JDK 是 1.8版本;
    可见JDK 提供的构造函数包括最后一个全参构造一共有四种
    第一种舍弃了线程构建以及拒绝策略,
    第二种舍弃了拒绝策略,
    第三种线程构造,

    第一种
    可见 舍弃的线程构造以及拒绝策略均采用了默认的方式
    在这里插入图片描述
    而拒绝策略默认为
    abort 直接丢弃并抛异常
    在这里插入图片描述
    第二种
    类似的使用了默认的拒绝策略
    在这里插入图片描述
    第三种
    类似的使用了默认的线程构造
    在这里插入图片描述

    定义线程构建方式(名称或否守护线程)ThreadFactory

    第一种方式
    lambda

    threadPoolExecutor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500),
                    r->new Thread(r, "my-taskPoolThreadA-"+r.hashCode()) , new ThreadPoolExecutor.CallerRunsPolicy());
    
    • 1
    • 2

    构建方式为:
    r->new Thread(r, “my-taskPoolThreadA-”+r.hashCode())

    运行后
    在这里插入图片描述
    第二种,仿照源码的方式创建:
    可以参考源码中默认工厂的创建方式,查看路径如下:
    在这里插入图片描述
    进入
    在这里插入图片描述
    再进入 new 的类
    在这里插入图片描述
    可见自己创建一个工厂时需要实现 ThreadFactory 的接口

    做部分解读如下:
    在这里插入图片描述
    实际自己构造时完全可以先照搬下来只改改命名方式:

    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class MyThreadFactory 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;
    
        MyThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "my-pool-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }
    
        @Override
        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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    实际上我们完全可以根据自己的需要决定是否设置保护线程,即 重写的方法中 newThread(Runnable r) 中 内部属性我们可以通过我们设定的其他值来控制,可以整改添加一些构造函数,将一些我们想要的控制项通过构造方法或者其他方法的方式传入Factory 并在重写的方法内生效,使之得到应用。
    例如可以如下修改:

    为了外部调用构造函数定义为 public

    public class MyThreadFactory 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;
        private boolean nDaemon;
    
        public MyThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "my-pool-" +
                    poolNumber.getAndIncrement() +
                    "-thread-";
        }
    
        public MyThreadFactory(String prefix, boolean needDaemon){
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
    
            namePrefix = prefix + "-mythread-" + poolNumber.getAndIncrement();
    
            nDaemon = needDaemon;
        }
    
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (nDaemon)
                t.setDaemon(nDaemon);
    
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    如此一来可以在外部如下书写:

            threadPoolExecutor = new ThreadPoolExecutor(10, 20, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(500),
                    new MyThreadFactory("ACCName", false), new ThreadPoolExecutor.CallerRunsPolicy());
    
    • 1
    • 2
  • 相关阅读:
    互联网摸鱼日报(2022-11-08)
    100-150
    Nano 编辑器中,怎样保存和退出
    Day19 | 每天五道题
    我悟了!Mysql事务隔离级别其实是这样!
    毅速引领模具3D打印材料创新之路
    hmcl_HMCL安装与使用
    Docker 镜像
    LeetCode允许重复选择元素的组合
    RK3568开发板在工控工业物联网网关方面的应用
  • 原文地址:https://blog.csdn.net/weixin_44131922/article/details/126486435