JUC知识点总结(八)线程池面试知识点总结

15. 线程池

15.1 为什么要使用线程池?

  • 降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗

  • 提高响应速度:任务到达时,任务可以不需要等到线程创建就能立即执行

  • 提高线程的可管理性

15.2 线程池的体系结构:

java.util.concurrent.Executor : 负责线程的使用与调度的根接口

  • ExecutorService 子接口: 线程池的主要接口
    • ThreadPoolExecutor 线程池的实现类
    • ScheduledExecutorService 子接口:负责线程的调度
      • ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor,实现 ScheduledExecutorService

15.3 创建线程池的方法

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS, new ArrayBlockingQueue(10));
/***********************************************************************/
public ThreadPoolExecutor(int corePoolSize, //核心池大小大小
                              int maximumPoolSize, //最大容量
                              long keepAliveTime, //线程数大于corePoolSize后,空闲存活时间
                              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.acc = System.getSecurityManager() == null ?
            null :
            AccessController.getContext();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}   
15.3.1 线程池的拒绝策略

等待队列也已经满了,再也塞不下新任务了。同时线程池中的max线程数也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的解决这个问题。

  • AbortPolicy 默认 抛出RejectedExecutionException异常阻止系统正常运行

  • CallerRunsPolicy 该策略既不会抛出任务,也不会抛出异常,而是将某些任务交由调用者完成。

  • DiscardOldestPolicy 抛弃队列中等待最久的任务,然后把当前任务加入到队列中尝试再次提交当前任务。

  • DiscardPolicy 直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

15.3.2 线程池的工作队列
  • ArrayBlockingQueue (有界队列)

  • LinkedBlockingQueue (无界队列)可以指定对立的大小,也可以不指定,默认类似一个无限大小的容量(Integer.MAX_VALUE)

  • SynchronousQueue(同步队列) 不存储元素,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态, 吞吐量通常要高于LinkedBlockingQueue

  • DelayQueue(延迟队列) 一个任务定时周期的延迟执行的队列。根据指定的执行时间从小到大排序,否则根据插入到队列的先后排序

  • PriorityBlockingQueue(优先级队列)

有界队列即长度有限,满了以后ArrayBlockingQueue会插入阻塞。无界队列就是里面能放无数的东西而不会因为队列长度限制被阻塞,但是可能会出现OOM异常。

15.4 线程池的提交与关闭方法

  • threadPool.execute(Runnable task) 提交无返回值的方法
  • threadPool.submit(Callable task) 提交有返回值的方法,返回一个future对象
  • threadPool.shutdown() 等待任务执行完关闭
  • threadPool.shutdownNow() 立即关闭

15.5 线程池的底层工作原理

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

  • 调用execute()方法提交一个新任务到线程池,处理流程:

    • 判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程执行任务。
    • 判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在这个工作队列里。
    • 判断线程池的线程是否都处于工作状态。如果没有,则创建新的工作线程来执行任务。如果满了,则交给饱和策略来处理这个任务。
  • 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  • 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:

  • 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

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

以ThreadPoolExecutor执行execute方法举例,分为4种情况:

  • 如果当前运行线程数少于corePoolSize,则创建新线程来执行任务

  • 如果运行的线程等于或多余corePoolSize,则将任务加入BlockingQueue

  • 如果BlockingQueue已满,则创建新的线程来处理任务

  • 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用对应的策略

工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里 的任务来执行。

15.6 使用线程池的风险

  • 死锁:线程池引入了另一种死锁可能,所有池程都在执行已阻塞的等待队列中另一任务的执行结果的任务,但这一任务却因为没有未被占用的线程而不能运行。
  • 资源不足
  • 并发错误
  • 线程泄漏:当从池中一个线程执行任务后该线程却没有返回池时,会发生这种情况。
  • 请求过载

15.7 创建线程池的工具类 : Executors

线程池种类 特点
newFixedThreadPool() 创建固定大小的线程池,核心线程数和最大线程数大小一样,keepAliveTime为0,阻塞队列是LinkedBlockingQueue,处理CPU密集型的任务。
newCachedThreadPool() 核心线程数为0,最大线程数为Integer.MAX_VALUE,keepAliveTime为60s,阻塞队列是SynchronousQueue,并发执行大量短期的小任务。
newSingleThreadExecutor() 创建单个线程池。核心线程数和最大线程数大小一样且都是1,keepAliveTime为0,阻塞队列是LinkedBlockingQueue,按添加顺序串行执行任务。
newScheduledThreadPool() 创建固定大小的线程,最大线程数为Integer.MAX_VALU,阻塞队列是DelayedWorkQueue

注意:

  • FixedThreadPool 和 SingleThreadPool允许的请求队列(底层实现是LinkedBlockingQueue)长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM
  • CachedThreadPool 和 ScheduledThreadPool 允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

下一篇
JUC知识点总结(九)Fork/Join框架解析

发布了54 篇原创文章 · 获赞 11 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/dong_W_/article/details/105107107