暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

每日一面——谈谈你对线程池的理解(下)

有理想的菜鸡 2020-05-26
112

菜鸡每日一面系列打卡23

每天一道面试题目 

助力小伙伴轻松拿offer

坚持就是胜利,我们一起努力!


题目描述

谈谈你对线程池的理解(下)。


题目分析

在上一篇谈谈你对线程池的理解(上)中,菜鸡主要讲述线程池的概念,Java中的线程池以及线程池的生命周期。本文将结合源码介绍线程池的配置以及核心实现。


如果说上一篇是对线程池的整体把握,那么,这篇就是对线程池的细节剖析。接下来,随菜鸡一起去看看吧。


题目解答


01
线程池的配置


在上篇中,我们提到了四种常见的线程工厂。


  • newFixedThreadPool:创建固定长度的线程池,每当提交一个任务就创建一个线程,直到达到线程池的最大数量后不再变化。


  • newSingleThreadExecutor:单线程的Executor,创建单个工作线程执行任务,可以确保按照任务在队列中的顺序来串行执行。


  • newCachedThreadPool:创建可缓存的线程池,如果线程池的当前规模超过处理需求时,回收空闲线程,反之,创建新的线程。


  • newScheduledThreadPool:创建固定长度的线程池,并且能以延迟或定时的方式执行任务。


它们的实现都或直接或间接地使用到了一个非常重要的类ThreadPoolExecutor。这个类的构造方法设置了线程池的必要参数。我们从源码入手,看一下ThreadPoolExecutor类的构造参数有哪些必要参数。

    public class ThreadPoolExecutor extends AbstractExecutorService {


    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.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;
    }


    }


    由源代码可知,该构造方法有七个参数,我们来看一下每个参数的具体含义。


    • corePoolSize:线程池的基本大小,也即线程池的目标大小,即在没有任务执行时线程池的大小,并且只有在工作队列满了的情况下才会创建超出这个数量的线程。


    • maximumPoolSize:线程池的最大大小,表示可同时活动的线程数量的上限。


    • keepAliveTime:如果某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,并且当线程池的当前大小超过了基本大小时,这个线程将被终止。


    • unit:keepAliveTime的单位。


    • workQueue:用于保存等待执行任务的队列。ThreadPoolExecutor允许提供一个BlockingQueue来保存等待执行的任务。基本的任务排列方法有3种:

      • 无界队列:在任务急剧增加时容易导致资源耗尽;

      • 有界队列:在队列满之需要相应的饱和策略来应对;

      • 同步移交:在线程池达到最大线程数且无空闲队列时需要相应的饱和策略来应对。


    • threadFactory:线程工厂,在ThreadFactory中只定义了一个方法newThread,每当线程池需要创建一个新线程时都会调用该方法。


    • handler:饱和策略。主要有4种:

      • AbortPolicy:终止,抛出RejectedExecutionException,默认饱和策略;

      • CallerRunsPolicy:将某些任务回退到调用者,降低新任务的流量;

      • DiscardPolicy:直接丢弃;

      • DiscardOldestPolicy:丢弃最早的未被处理的任务。


    02
    源码分析


    了解线程池的参数之后,我们来看看ThreadPoolExecutor类的核心部分源码。

      public class ThreadPoolExecutor extends AbstractExecutorService {
      // 执行
      public void execute(Runnable command) {
      if (command == null)
                  throw new NullPointerException();
      int c = ctl.get();
              // 当前任务数小于线程池的基本大小
      if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
      return;
      c = ctl.get();
      }
      // 线程池状态isRunning并且工作队列可以加入
      if (isRunning(c) && workQueue.offer(command)) {
      int recheck = ctl.get();
      if (! isRunning(recheck) && remove(command))
      reject(command);
      else if (workerCountOf(recheck) == 0)
      addWorker(null, false);
      }
              // 通过addWorker(command, false)新建线程执行任务,若失败则拒绝。
      else if (!addWorker(command, false))
      reject(command);
      }

      // 平缓关闭
      public void shutdown() {
      final ReentrantLock mainLock = this.mainLock;
      mainLock.lock();
      try {
      checkShutdownAccess();
      // 状态为SHUTDOWN
      advanceRunState(SHUTDOWN);
      interruptIdleWorkers();
      onShutdown();
      } finally {
      mainLock.unlock();
      }
      tryTerminate();
      }

      // 粗暴关闭
      public List<Runnable> shutdownNow() {
      List<Runnable> tasks;
      final ReentrantLock mainLock = this.mainLock;
      mainLock.lock();
      try {
      checkShutdownAccess();
      // 状态为STOP
      advanceRunState(STOP);
      interruptWorkers();
      tasks = drainQueue();
      } finally {
      mainLock.unlock();
      }
      tryTerminate();
      return tasks;
          }
      }


      上述主要分享了最常用的execute方法以及上一篇中提到的关闭线程池两种方式的源码。菜鸡在关键位置做了注释,在此就不再赘述。


      其实,如果静下心来看,小伙伴们就会发现,JDK的源码条理性是很好的,各种设计的思维细细品味也都能有所领悟,因此,学习优秀的源码对个人成长是非常有帮助的。


      如果不知道哪些是优秀的源码,那就从JDK源码看起,从常用的类库的源码看起,在学习原理的同时,学习设计,妙啊……


      相关链接

      每日一面——谈谈你对线程池的理解(上)


      以上便是菜鸡对线程池的总结下篇,供大家参考,结合上篇食用,效果更佳!



      学习 | 工作 | 分享

      👆长按关注“有理想的菜鸡

      只有你想不到,没有你学不到
      文章转载自有理想的菜鸡,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论