• 3. executors



      线程池可以说是面试热门问题了,所以是必须要搞懂的东西,但是理顺比较难啊。我勉强试试。

    固定线程池

      固定数量线程池,就是可以做到每次执行固定个数的线程。比如线程池大小为4.那么每次都有四个线程在运行。
    用时间戳记录,会更加直观。

    package com.youngthing.interpolation.test;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 9/4/2022 10:17 PM 创建
     *
     * @author 花书粉丝
     */
    public class ExecutorDemo {
        private static final AtomicInteger I = new AtomicInteger();
    
        public static void main(String[] args) {
            final ExecutorService executorService = Executors.newFixedThreadPool(4);
            final Runnable runnable = () -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("i = " + I.getAndIncrement() + ":"+ System.currentTimeMillis()/1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
    
            for (int j = 0; j < 12; j++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
        }
    }
    
    
    
    • 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

    打印结果:

    i = 0:1662303030
    i = 3:1662303030
    i = 2:1662303030
    i = 1:1662303030
    i = 6:1662303031
    i = 5:1662303031
    i = 4:1662303031
    i = 7:1662303031
    i = 8:1662303032
    i = 9:1662303032
    i = 11:1662303032
    i = 10:1662303032
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

      可见线程池大小为4的时候,每次就只有四个线程在运行。
      newSingleThreadExecutor,这个不过是将大小固定为1的固定线程池而已,不多赘述。

    缓存线程池

      缓存线程池不能指定线程数量,所以更加灵活。它在任务增加时会多创建一些线程,在任务减少时,会回收一些线程,可以说是一种“弹性计算”。这个回收时间默认是60秒。把上面代码改一改,会发现缓存的线程池是几乎同时执行所有线程的,代码如下:

    package com.youngthing.interpolation.test;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 9/4/2022 10:17 PM 创建
     *
     * @author 花书粉丝
     */
    public class CachedExecutorDemo {
        private static final AtomicInteger I = new AtomicInteger();
    
        public static void main(String[] args) {
            final ExecutorService executorService = Executors.newCachedThreadPool();
            final Runnable runnable = () -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("i = " + I.getAndIncrement() + ":"+ System.currentTimeMillis()/1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
    
            for (int j = 0; j < 12; j++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
        }
    }
    
    
    
    • 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

      执行结果:

    i = 4:1662302891
    i = 0:1662302891
    i = 8:1662302891
    i = 7:1662302891
    i = 2:1662302891
    i = 11:1662302891
    i = 1:1662302891
    i = 5:1662302891
    i = 9:1662302891
    i = 6:1662302891
    i = 3:1662302891
    i = 10:1662302891
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    定时线程池

      四大线程池介绍了三个,现在这是第四个。这个有点特殊,上述三个都是返回ExecutorService,而newScheduledThreadPool返回的是ScheduledExecutorService,是ExecutorService的子类,提供了一些定时的方法比如延迟方法。同时也支持固定线程池的功能,把上面例子改写,记录下启动时间就知道了:

    package com.youngthing.interpolation.test;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ScheduledExecutorService;
    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 9/4/2022 10:17 PM 创建
     *
     * @author 花书粉丝
     */
    public class ScheduleExecutorDemo {
        private static final AtomicInteger I = new AtomicInteger();
    
        public static void main(String[] args) {
            final ScheduledExecutorService executorService = Executors.newScheduledThreadPool(4);
            final Runnable runnable = () -> {
    
                try {
                    Thread.sleep(1000);
                    System.out.println("i = " + I.getAndIncrement() + ":"+ System.currentTimeMillis()/1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
    
            };
            System.out.println("current:" + System.currentTimeMillis() / 1000);
            for (int j = 0; j < 12; j++) {
                executorService.schedule(runnable, 2l, TimeUnit.SECONDS);
            }
            executorService.shutdown();
        }
    }
    
    
    • 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

      因为有点误差,所以我这里记录的延迟了三秒启动,结果如下:

    current:1662302723
    i = 1:1662302726
    i = 3:1662302726
    i = 0:1662302726
    i = 2:1662302726
    i = 4:1662302727
    i = 6:1662302727
    i = 7:1662302727
    i = 5:1662302727
    i = 8:1662302728
    i = 11:1662302728
    i = 9:1662302728
    i = 10:1662302728
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

      所以他是固定线程池和定时线程池的混合版本。类似这样的混合版本还有newSingleThreadScheduledExecutor,这个是单线程池和定时线程池的混合版本。

    工厂参数

      在很多公司的开发规范中,线程池必须自己取名字,这样日志里比较好追踪,其实这个比较简单,因为上述四大线程池,都有个工厂参数,在工厂参数里自己定一个线程名字规则就行了。如以下例子:

    package com.youngthing.interpolation.test;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.ThreadFactory;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 9/4/2022 10:17 PM 创建
     *
     * @author 花书粉丝
     */
    public class ExecutorFactoryDemo {
        private static final AtomicInteger I = new AtomicInteger();
    
        public static void main(String[] args) {
            final ExecutorService executorService = Executors.newFixedThreadPool(4, new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    final Thread thread = new Thread(Thread.currentThread().getThreadGroup(), r, "OK-" + I.getAndIncrement());
                    return thread;
                }
            });
            final Runnable runnable = () -> {
                try {
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis() / 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
    
            for (int j = 0; j < 12; j++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
        }
    }
    
    
    • 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

      打印结果如下:

    OK-2:1662303969
    OK-0:1662303969
    OK-1:1662303969
    OK-3:1662303969
    OK-0:1662303970
    OK-2:1662303970
    OK-1:1662303970
    OK-3:1662303970
    OK-1:1662303971
    OK-3:1662303971
    OK-0:1662303971
    OK-2:1662303971
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

      从名字上看得一清二楚,就只创建了四个线程,而且线程名字也是我们自定义的名字。

    ThreadPoolExecutor

      ExecutorService有两个非常重要的实现类,一个是ThreadPoolExecutor,另一个是ForkJoinPool。ForkJoinPool我不会在这篇博客里讲,因为我准备写另外一篇博客去介绍ForkJoinPool。所以我这里只讲ThreadPoolExecutor,这个类在面试时是热门问题,经常被问起,主要问的是参数细节。那我们来看看总共多少个参数吧!ThreadPoolExecutor参数最多的构造器是以下方法:

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

      总共7个参数,我将一一介绍。
      前两个重要参数是corePoolSize与maximumPoolSize,corePoolSize和maximumPoolSize控制线程池的大小。当线程数量小于corePoolSize时,会创建新线程去执行任务。当队列满的时候,线程数量大于corePoolSize且小于maximumPoolSize时,也会创建新线程去执行任务。当设置的corePoolSize和maximumPoolSize相等时,这时候就是一个固定尺寸的线程池了。当然,这两个参数可以用set方法修改,并不是传入构造器就不能改了。
      接下来的两个参数keepAliveTime和unit是活跃时间。在线程数量超过corePoolSize时,溢出的线程,如果剩余的空闲时间超过keepAliveTime,那么将会中止,然后资源得到释放,保持一个较小的线程池。
      再看看workQueue这个参数,这里需要注意的是线程拒绝策略。

    1. 当线程数小于corePoolSize时,创建新线程。
    2. 当线程数等于corePoolSize时,加入队列。
    3. 当线程数等于corePoolSize时,且队列满了时,创建新线程。
    4. 当线程数等于maximumPoolSize时,拒绝新任务。

      在实践中有三种队列策略:

    1. 接力策略,使用SynchronousQueue,这个队列可以理解为长度只能为1的队列,相当于一个小缓冲。
    2. 无边界策略,使用LinkedBlockingQueue,因为这个阻塞队列不限制大小,但是这个时候maximumPoolSize参数就没有效果了。
    3. 有边界策略。使用 ArrayBlockingQueue,这是实际生产中最常用的策略。

      而threadFactory我就不说了,前一段落说过了。直接说下一个重点,handler。拒绝任务的场景上面说过,在拒绝时会执行拒绝策略,如果时间充裕,可以自己实现一个拒绝策略,但如果时间不充裕,可以有JDK自带的四种拒绝策略:

    1. 中断策略,实现类为ThreadPoolExecutor.AbortPolicy,在拒绝时抛出异常,这也是默认实现
    2. 调用者执行策略,实现类为ThreadPoolExecutor.CallerRunsPolicy,由调用者执行溢出的任务。
    3. 丢弃策略,实现类为ThreadPoolExecutor.DiscardPolicy,直接放弃新加的任务。
    4. 丢弃最旧任务策略,实现类为ThreadPoolExecutor.DiscardOldestPolicy,直接放弃队列头部的任务,也就是等待最久的任务。
  • 相关阅读:
    商标申请注册交费就一定会下注册证?
    【Halcon图像拟合圆】
    java学习 File类 、IO流
    Java基础练习(运算符的使用、Java的连续输入与输出、Java中分别输出整数、小数、字符串)
    查看docker容器中java进程堆栈信息
    linux(14)之audit子系统
    在VmWare中安装Centos7
    微信小程序——项目成员管理、小程序上传、发布步骤
    windows下安装RabbitMQ
    java题库——继承和多态
  • 原文地址:https://blog.csdn.net/m0_66201040/article/details/126695611