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 之间。

  

 

posted @ 2019-08-15 23:01  与君共舞  阅读(903)  评论(0编辑  收藏  举报