多线程与高并发(七)--线程池

Executor

  • 提供了void execute(Runnable command);方法。执行command任务,将任务的定义以及运行分离。

ExecutorService

  • 继承了Executor,完善了任务执行器的生命周期。
  • 提供了submit方法(异步执行任务),将任务完成的返回值封装到Future中。

Callable

  • 和Runnable类似,提供了call方法。与Runnable的run方法不同的是,call方法有返回值。

Future

  • 代表任务执行的结果,将任务完成的返回值封装到future。
  • 通过get()方法获取任务返回值。
    • get是阻塞方法,知道可以获取结果才会往下执行。

FutureTask

  • 既是一个Future也是一个Callable(实现了RunnableFuture,RunnableFuture继承了Runnable以及Future)。

CompletableFuture

  • 提供了对单个以及批量任务的管理方案。

ThreadPoolExecutor

  • 继承自AbstractExecutorService(AbstractExecutorService实现了ExecutorService,ExecutorService继承了Executor)。
  • 内部维护了两个集合,一个是线程集合,一个是任务集合。
  • 七个参数简介
ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runn able> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
    • corePoolSize:核心线程数,核心线程是即使空闲时间超过了生存时间也不会被回收。
    • maximumPoolsize:最大线程数,最多有多少个线程。
    • keepAliveTime:生存时间,线程超过多久没有执行任务会被回收,数量等于核心线程数时不再回收线程。
    • unit:生存时间的单位
    • workQueue:任务队列,BlockingQueue(使用不同的BlockingQueue会产生不同的线程池)。
    • threadFactory:线程工厂,需要实现ThreadFactory,可以自定义实现。
    • handler:拒绝策略,线程池所有线程都在忙碌状态,而且任务队列已满,需要执行拒绝策略。拒绝策略可以自定义,默认提供了四种(一般情况都是自定义策略)。
      • Abort:抛异常。
      • Discard:扔掉,不抛异常。
      • DiscardOldest:扔掉排队时间最久的。
      • CallerRuns:调用者处理任务。
      • 自定义的通常是记录日志并且记录到MySQL、Redis或者kafka中。
  • 源码解析:
    • 常量
      // ctl 可以看做一个int类型的数字,高3位表示线程池状态,低29位表示worker数量
      private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
      // COUNT_BITS,Integer.size=32,所以COUNT_BITS=29
      private static final int COUNT_BITS = Integer.SIZE - 3;
      // CAPACITY,线程池允许的最大线程数。1左移29位,然后减1,即为2^29-1
      private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
      // runState is stored in the high-order bits
      // 线程池有5中状态,按大小顺序如下:RUNNING<SHUTDOWN<STOP<TIDYING<TERMINATED
      private static final int RUNNING    = -1 << COUNT_BITS;
      private static final int SHUTDOWN   =  0 << COUNT_BITS;
      private static final int STOP       =  1 << COUNT_BITS;
      private static final int TIDYING    =  2 << COUNT_BITS;
      private static final int TERMINATED =  3 << COUNT_BITS;

       

    • executor方法
      public void execute(Runnable command) {
          if (command == null)
              throw new NullPointerException();
          /*
           * Proceed in 3 steps:
           *
           * 1. If fewer than corePoolSize threads are running, try to
           * start a new thread with the given command as its first
           * task.  The call to addWorker atomically checks runState and
           * workerCount, and so prevents false alarms that would add
           * threads when it shouldn't, by returning false.
           *
           * 2. If a task can be successfully queued, then we still need
           * to double-check whether we should have added a thread
           * (because existing ones died since last checking) or that
           * the pool shut down since entry into this method. So we
           * recheck state and if necessary roll back the enqueuing if
           * stopped, or start a new thread if there are none.
           *
           * 3. If we cannot queue task, then we try to add a new
           * thread.  If it fails, we know we are shut down or saturated
           * and so reject the task.
           */
          int c = ctl.get();
          // worker数量比核心线程数小,直接创建worker执行任务
          if (workerCountOf(c) < corePoolSize) {
              if (addWorker(command, true))
                  return;
              // 多线程场景下,值有可能会被别的线程修改,所以要再取一次
              c = ctl.get();
          }
          // worker数量超过核心线程数,任务直接进入队列
          if (isRunning(c) && workQueue.offer(command)) {
              int recheck = ctl.get();
              // 线程池状态不是RUNNING状态,说明执行过shutdown命令,需要对新加入的任务执行reject()操作。
              // 这为什么需要recheck,是因为任务入队列前后,线程池的状态可能会发生变化。
              if (! isRunning(recheck) && remove(command))
                  reject(command);
              // 这为什么需要判断0值,主要是在线程池构造方法中,核心线程数允许为0
              else if (workerCountOf(recheck) == 0)
                  addWorker(null, false);
          }
          /**
           * 如果线程池不是运行状态,或者任务进入队列失败,则尝试创建worker执行任务。
           * 这有三点需要注意:
           * 1.线程池不是运行状态时,addWorker内部会判断线程池状态
           * 2.addWorker第2个参数表示是否创建核心线程
           * 3.addWorker返回false,则说明任务执行失败,需要执行reject操作
           */
          else if (!addWorker(command, false))
              reject(command);
      }

       

    • addWorker方法
      /**
       * Checks if a new worker can be added with respect to current
       * pool state and the given bound (either core or maximum). If so,
       * the worker count is adjusted accordingly, and, if possible, a
       * new worker is created and started, running firstTask as its
       * first task. This method returns false if the pool is stopped or
       * eligible to shut down. It also returns false if the thread
       * factory fails to create a thread when asked.  If the thread
       * creation fails, either due to the thread factory returning
       * null, or due to an exception (typically OutOfMemoryError in
       * Thread.start()), we roll back cleanly.
       *
       * @param firstTask the task the new thread should run first (or
       * null if none). Workers are created with an initial first task
       * (in method execute()) to bypass queuing when there are fewer
       * than corePoolSize threads (in which case we always start one),
       * or when the queue is full (in which case we must bypass queue).
       * Initially idle threads are usually created via
       * prestartCoreThread or to replace other dying workers.
       *
       * @param core if true use corePoolSize as bound, else
       * maximumPoolSize. (A boolean indicator is used here rather than a
       * value to ensure reads of fresh values after checking other pool
       * state).
       * @return true if successful
       */
      private boolean addWorker(Runnable firstTask, boolean core) {
          retry:
          // 外层自旋
          for (;;) {
              int c = ctl.get();
              int rs = runStateOf(c);
      
              /**
               * (rs > SHUTDOWN) ||
               * (rs == SHUTDOWN && firstTask != null) ||
               * (rs == SHUTDOWN && workQueue.isEmpty())
               * 1.线程池状态大于SHUTDOWN时,直接返回false
               * 2.线程池状态等于SHUTDOWN,且firstTask不为null,直接返回false
               * 3.线程池状态等于SHUTDOWN,且队列为空,直接返回false
               */
              // Check if queue empty only if necessary.
              if (rs >= SHUTDOWN &&
                      ! (rs == SHUTDOWN &&
                              firstTask == null &&
                              ! workQueue.isEmpty()))
                  return false;
      
              // 内层自旋
              for (;;) {
                  int wc = workerCountOf(c);
                  // worker数量超过容量,直接返回false
                  if (wc >= CAPACITY ||
                          wc >= (core ? corePoolSize : maximumPoolSize))
                      return false;
                  // 使用CAS的方式增加worker数量。
                  // 若增加成功,则直接跳出外层循环进入到第二部分
                  if (compareAndIncrementWorkerCount(c))
                      break retry;
                  c = ctl.get();  // Re-read ctl
                  // 线程池状态发生变化,对外层循环进行自旋
                  if (runStateOf(c) != rs)
                      continue retry;
                  // 其他情况,直接内层循环进行自旋即可
                  // else CAS failed due to workerCount change; retry inner loop
              }
          }
      
          boolean workerStarted = false;
          boolean workerAdded = false;
          Worker w = null;
          try {
              w = new Worker(firstTask);
              final Thread t = w.thread;
              if (t != null) {
                  final ReentrantLock mainLock = this.mainLock;
                  // worker的添加必须是串行的,因此需要加锁
                  mainLock.lock();
                  try {
                      // Recheck while holding lock.
                      // Back out on ThreadFactory failure or if
                      // shut down before lock acquired.
                      // 这里需要重新检查线程池状态
                      int rs = runStateOf(ctl.get());
      
                      if (rs < SHUTDOWN ||
                              (rs == SHUTDOWN && firstTask == null)) {
                          // worker已经调用过了start()方法,则不在创建worker
                          if (t.isAlive()) // precheck that t is startable
                              throw new IllegalThreadStateException();
                          // worker创建并添加到workers成功
                          workers.add(w);
                          // 国内新年largestPoolSize变量
                          int s = workers.size();
                          if (s > largestPoolSize)
                              largestPoolSize = s;
                          workerAdded = true;
                      }
                  } finally {
                      mainLock.unlock();
                  }
                  // 启动worker线程
                  if (workerAdded) {
                      t.start();
                      workerStarted = true;
                  }
              }
          } finally {
              // worker线程启动失败,说明线程池状态发生了变化(关闭操作被执行),需要进行shutdown相关操作
              if (! workerStarted)
                  addWorkerFailed(w);
          }
          return workerStarted;
      }

       

    • 线程池worker任务单元:Worker类继承了AbstractQueueSynchronizer(AQS),同时实现了Runnable接口。是一个任务的同时是一把锁。

ForkJoinPool

  • 分解汇总的任务。
  • 用很少的线程可以执行很多的任务(子任务)TPE做不到先执行子任务。
  • CPU密集型

Executors

  线程工具类,可以看做是线程池的工厂。

  • 线程池数量的选择
    • 如果线程池中线程的数量过多,最终它们会竞争稀缺的处理器和内存资源,浪费大量的时间在上下文切换。反之,如果线程的数目过少,处理器的一些和可能就无法充分使用。通过压测来进行判断。
  • SingleThreadExecutor
    • 线程池中只有一个线程。
    • 为什么要有单线程的线程池?
      • 有任务队列,可以管理线程的生命周期。
  • CachedThreadPool
    • 核心线程数为0,最大线程数时Integer.MAX_VALUE,生存时间为60s,任务队列是SynchronousQueue(来一个任务就会被线程执行)。
  • FixedThreadPool
    • 核心线程=最大线程(固定数量线程池)。
  • Cached VS Fixed
    • 如果任务请求不平均,使用Cached。
    • 如果任务请求比较平稳,使用Fixed。
  • ScheduledThreadPool
    • 专门用来执行定时任务的线程池。
    • 指定核心线程数,最大线程数是整数最大值,任务队列是DelayedWorkQueue(可以指定经过多长时间之后运行)。
    • 经常使用的定时器框架:quarts。
  • WorkStealingPool(本质是一个ForkJoinPool)
    • 和以上线程池不同的是,WorkStealingPool线程池的每一个线程都有自己对应的任务队列。当某个线程执行完自己的任务之后会去别的线程的任务队列取任务。
    • 往任务队列放任务以及消费自己任务队列的任务时不需要加锁,取别的线程的任务队列的任务时需要加锁。
    • Executors中有WorkStealingPool是为了提供一个方便使用的接口
  • List.parallelStream:并行流
  • 面试题:假如提供一个闹钟服务,订阅这个服务的人特别多,大概有10亿左右,如何优化?
    • 将任务分发到边缘的服务器上,在边缘服务器通过多线程去消费处理。
  • 并行和并发的区别:
    • 如果某个系统支持两个或者多个动作(Action)同时存在,那么这个系统就是一个并发系统。如果某个系统支持两个或者多个动作同时执行,那么这个系统就是一个并行系统。并发系统与并行系统这两个定义之间的关键差异在于“存在”这个词。 --- 《并发的艺术》 — 〔美〕布雷谢斯
posted @ 2021-02-17 21:56  January01  阅读(52)  评论(0)    收藏  举报