zoukankan      html  css  js  c++  java
  • java面试-线程池使用过吗,谈谈对ThreadPoolExector的理解

     一、架构说明:

    二、为什么使用线程池,优势是什么?

    线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,那么超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

    特点:线程复用、控制最大并发数量、管理线程

    优点:

    • 降低资源消耗。通过重复利用已创建的线程来降低线程创建和销毁造成的消耗。
    • 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
    • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅仅会消耗系统资源,还会降低体统的稳定性,使用线程可以进行统一分配,调优和监控。

    2、特点:

    newFixedThreadPool():执行长期任务,性能好很多

    newSingleThreadExecutor():一个任务一个任务顺序执行

    newCachedThreadPool():是一种用来处理大量短时间工作任务的线程池

    代码演示:

    public class MyThreadPoolDemo {
        public static void main(String[] args) {
    
    //        ExecutorService executorService = Executors.newFixedThreadPool(5); //一次有5个处理线程
    //        ExecutorService executorService = Executors.newSingleThreadExecutor();
    //        ExecutorService executorService = Executors.newCachedThreadPool();
    
            //真实项目中使用
            ExecutorService executorService = new ThreadPoolExecutor(2, 3, 1L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(5),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardOldestPolicy());
            try {
                //模拟10个用户来办理业务
                for (int i = 0; i < 12; i++) {
    
    //                try {
    //                    TimeUnit.SECONDS.sleep(1);
    //                } catch (InterruptedException e) {
    //                    e.printStackTrace();
    //                }
                    executorService.execute(() -> {
                        System.out.println(Thread.currentThread().getName()+" 办理业务");
                    });
                }
    
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                executorService.shutdown();
            }
        }
    }  

    三、ThreadPoolExecutor

        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  ThreadFactory threadFactory,
                                  RejectedExecutionHandler handler) {
            if (corePoolSize < 0 ||
                maximumPoolSize <= 0 ||
                maximumPoolSize < corePoolSize ||
                keepAliveTime < 0)
                throw new IllegalArgumentException();
            if (workQueue == null || threadFactory == null || handler == null)
                throw new NullPointerException();
            this.corePoolSize = corePoolSize;
            this.maximumPoolSize = maximumPoolSize;
            this.workQueue = workQueue;
            this.keepAliveTime = unit.toNanos(keepAliveTime);
            this.threadFactory = threadFactory;
            this.handler = handler;
        }  

    四、线程池七大参数介绍

    corePoolSize:线程池的核心线程数

    maximumPoolSize:线程池的最大线程数,此值必须大于等于1

    keepAliveTime:多余的空闲线程的存活时间,线程池数量超过corePoolSize,空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到剩下corePoolSize个线程为止

    TimeUnit:keepAliveTime的单位

    workQueue:任务队列,被提交但是尚未被执行的任务

    threadFactory:设置创建线程的工厂

    RejectedExecutionHandler:拒绝策略,当提交任务数超过maxmumPoolSize+workQueue之和时,任务会交给RejectedExecutionHandler 来处理

    五、线程池底层工作原理

    1、在创建了线程池后,等待提交过来的任务请求。

    2、ThreadPoolExecutor执行execute方法,线程池会做如下判断:

    • 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取全局锁)。
    • 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
    • 如果无法将任务加入BlockingQueue(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁)。
    • 如果创建新线程将使当前运行的线程超出maximumPoolSize,线程池会启用拒绝策略来执行。

    3、当一个线程完成任务,会从队列中取下一个任务来执行

    4、当线程空闲时间达到keepAliveTime值时,线程池会判断,如果当前线程数大于corePoolSize,那么这个线程就会被停掉。

       所以线程池的所有任务完成后最终会收缩到corePoolSize的大小。

    六、线程池的4种拒绝策略

    当队列和线程池都满了,说明线程池处于饱和状态,那么必须采取一种策略处理提交的新任务

    • AbortPolicy(默认):直接抛出现RejectedExecutionException异常。
    • CallerRunsPolicy:该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
    • DiscardOldestPolicy:丢弃队列里等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
    • DiscardPolicy:直接丢弃掉,不予任何处理也不抛出异常。

    六、工作中线程池实际使用

    public class ThreadPoolExecutorDemo {
        public static void main(String[] args) {
            ExecutorService executorService = new ThreadPoolExecutor(2, 3, 1L, TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(5),
                    Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.DiscardPolicy());
        }
    }  

    六、线程池配置合理线程数

     查看电脑cpu核数:

    public class CPUCoresDemo {
        public static void main(String[] args) {
            System.out.println(Runtime.getRuntime().availableProcessors());
        }
    }

    1、CPU密集型:任务需要大量的运算,而没有阻塞,CPU一直全速运行。

    CPU密集型任务配置尽可能的少的线程数量,

    公式:CPU核数 + 1个线程的线程池。

    2、IO密集型:任务需要大量的IO,即大量的阻塞

    由于IO密集型任务线程并不是一直在执行任务,可以多分配一点线程数,如 CPU * 2 。

    使用公式:CPU 核数/(1-阻塞系数);其中阻塞系数在 0.8 ~ 0.9 之间。

      

  • 相关阅读:
    门面模式 Facade
    适配器模式 Adapter
    建造者模式 Builder Mode
    原型模式 ProtoType
    抽象工厂模式 Abstract Factory
    工厂方法模式 Factory Method
    简单工厂模式 SimpleFactory
    java设计模式之代理设计模式(Proxy)
    java工厂设计模式初步
    java中的接口概念
  • 原文地址:https://www.cnblogs.com/wjh123/p/11192656.html
Copyright © 2011-2022 走看看