likes
comments
collection
share

ExecutorService & RxJava Schedulers 原理

作者站长头像
站长
· 阅读数 3

线程池 ExecutorService 原理

ExecutorService 已经是老生常谈的话题了,也是面试八股文必问的内容,本文只是记录一些分析代码过程中特殊的点供以后参考。

如果想全面了解 ExecutorService,强烈建议参考 Java线程池实现原理及其在美团业务中的实践

ExecutorService 状态机

ExecutorService & RxJava Schedulers 原理

ExecutorService 核心概念

ExecutorService & RxJava Schedulers 原理

调用入口 execute()

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();
    if (workerCountOf(c) < corePoolSize) {
      if (addWorker(command, true))
        return;
      c = ctl.get();
    }
    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);
    }
    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);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                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;
                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)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Worker 是什么?

 /**
     * Class Worker mainly maintains interrupt control state for
     * threads running tasks, along with other minor bookkeeping.
     * This class opportunistically extends AbstractQueuedSynchronizer
     * to simplify acquiring and releasing a lock surrounding each
     * task execution.  This protects against interrupts that are
     * intended to wake up a worker thread waiting for a task from
     * instead interrupting a task being run.  We implement a simple
     * non-reentrant mutual exclusion lock rather than use
     * ReentrantLock because we do not want worker tasks to be able to
     * reacquire the lock when they invoke pool control methods like
     * setCorePoolSize.  Additionally, to suppress interrupts until
     * the thread actually starts running tasks, we initialize lock
     * state to a negative value, and clear it upon start (in
     * runWorker).
     */
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }
    
    		...
    
    }
  • Worker 本身实现了 Runnable 接口
  • Worker 中的线程启动后,实际执行的是 Worker 的 run()
  • firstTask 会在 runWoker() 中第一个执行(如果不是 null)

线程执行任务 runWorker()

/**
     * Main worker run loop.  Repeatedly gets tasks from queue and
     * executes them, while coping with a number of issues:
     *
     * 1. We may start out with an initial task, in which case we
     * don't need to get the first one. Otherwise, as long as pool is
     * running, we get tasks from getTask. If it returns null then the
     * worker exits due to changed pool state or configuration
     * parameters.  Other exits result from exception throws in
     * external code, in which case completedAbruptly holds, which
     * usually leads processWorkerExit to replace this thread.
     *
     * 2. Before running any task, the lock is acquired to prevent
     * other pool interrupts while the task is executing, and then we
     * ensure that unless pool is stopping, this thread does not have
     * its interrupt set.
     *
     * 3. Each task run is preceded by a call to beforeExecute, which
     * might throw an exception, in which case we cause thread to die
     * (breaking loop with completedAbruptly true) without processing
     * the task.
     *
     * 4. Assuming beforeExecute completes normally, we run the task,
     * gathering any of its thrown exceptions to send to afterExecute.
     * We separately handle RuntimeException, Error (both of which the
     * specs guarantee that we trap) and arbitrary Throwables.
     * Because we cannot rethrow Throwables within Runnable.run, we
     * wrap them within Errors on the way out (to the thread's
     * UncaughtExceptionHandler).  Any thrown exception also
     * conservatively causes thread to die.
     *
     * 5. After task.run completes, we call afterExecute, which may
     * also throw an exception, which will also cause thread to
     * die. According to JLS Sec 14.20, this exception is the one that
     * will be in effect even if task.run throws.
     *
     * The net effect of the exception mechanics is that afterExecute
     * and the thread's UncaughtExceptionHandler have as accurate
     * information as we can provide about any problems encountered by
     * user code.
     *
     * @param w the worker
     */
    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread(); //这里就是 worker 绑定的 thread
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        task.run();
                    } catch (RuntimeException x) {
                        thrown = x; throw x;
                    } catch (Error x) {
                        thrown = x; throw x;
                    } catch (Throwable x) {
                        thrown = x; throw new Error(x);
                    } finally {
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);// 销毁线程
        }
    }
  • Thread wt = Thread.currentThread() 就是 worker 绑定的 thread
  • 进入 Looper,通过 getTask() 获取任务
  • 如果任务不是 null,则执行任务
  • 直到 getTask() 取到 null,退出 looper,执行 processWorkerExit()

队列中获取任务 getTask()

 /**
     * Performs blocking or timed wait for a task, depending on
     * current configuration settings, or returns null if this worker
     * must exit because of any of:
     * 1. There are more than maximumPoolSize workers (due to
     *    a call to setMaximumPoolSize).
     * 2. The pool is stopped.
     * 3. The pool is shutdown and the queue is empty.
     * 4. This worker timed out waiting for a task, and timed-out
     *    workers are subject to termination (that is,
     *    {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
     *    both before and after the timed wait, and if the queue is
     *    non-empty, this worker is not the last thread in the pool.
     *
     * @return task, or null if the worker must exit, in which case
     *         workerCount is decremented
     */
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
  • 如果 线程池状态 >= sSHUTDOWN 且 (线程池状态 >= STOP 或 队列已空),返回 null(当线程池被 shutdown 时,会打破死循环)
  • 当前 Worker 从队列中取一个任务或者进入阻塞

Worker 销毁 processWorkerExit()

/**
     * Performs cleanup and bookkeeping for a dying worker. Called
     * only from worker threads. Unless completedAbruptly is set,
     * assumes that workerCount has already been adjusted to account
     * for exit.  This method removes thread from worker set, and
     * possibly terminates the pool or replaces the worker if either
     * it exited due to user task exception or if fewer than
     * corePoolSize workers are running or queue is non-empty but
     * there are no workers.
     *
     * @param w the worker
     * @param completedAbruptly if the worker died due to user exception
     */
    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

资源回收 shutdown() & shutdownNow()

/**
     * Initiates an orderly shutdown in which previously submitted
     * tasks are executed, but no new tasks will be accepted.
     * Invocation has no additional effect if already shut down.
     *
     * <p>This method does not wait for previously submitted tasks to
     * complete execution.  Use {@link #awaitTermination awaitTermination}
     * to do that.
     *
     * @throws SecurityException {@inheritDoc}
     */
public void shutdown() {
  final ReentrantLock mainLock = this.mainLock;
  mainLock.lock();
  try {
    checkShutdownAccess();
    advanceRunState(SHUTDOWN);
    interruptIdleWorkers();
    onShutdown(); // hook for ScheduledThreadPoolExecutor
  } finally {
    mainLock.unlock();
  }
  tryTerminate();
}
 /**
     * Attempts to stop all actively executing tasks, halts the
     * processing of waiting tasks, and returns a list of the tasks
     * that were awaiting execution. These tasks are drained (removed)
     * from the task queue upon return from this method.
     *
     * <p>This method does not wait for actively executing tasks to
     * terminate.  Use {@link #awaitTermination awaitTermination} to
     * do that.
     *
     * <p>There are no guarantees beyond best-effort attempts to stop
     * processing actively executing tasks.  This implementation
     * cancels tasks via {@link Thread#interrupt}, so any task that
     * fails to respond to interrupts may never terminate.
     *
     * @throws SecurityException {@inheritDoc}
     */
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
  • shutdown 和 shutdownNow 的区别是
    • shutdown 状态机会扭转到 SHUTDOWN,表明当前线程池不接收新任务了,但是队列中遗留的任务会跑完
    • shutdownNow 会直接扭转到 STOP,会将当前队列中还存在的任务挤出到新的 list 中,并返回

Rx2.x IO Scheduler 原理

Rx1.x Rx2.x IO 线程池的原理大致相似,都是基于 ExecutorService 加了「过期队列」的机制。

核心概念

ExecutorService & RxJava Schedulers 原理

此图描述了 Rx 线程池和 ExecutorService 的关系:

  • 业务侧通过 IoScheduler 提交任务
  • CachedPool 会缓存一组 ThreadWorker,实际为 expiringWorkingQueue (ConcurrentLinkedQueue类型)
  • 每个 ThreadWorker 会有 expiredTime 属性,一但过期就会被移除
  • 无论是通过 Schedulers.io().scheduleDirect 或者 subscribeOn(Schedulers.io()) 的方式使用线程池,最终都会调用 scheduler#scheduleDirect,这个方法做了:
  • new EventLoopWorker,这是 ThreadWorker 的装饰类,内部会通过 CachedWorkPool 选择创建还是复用 ThreadWorker
  • CachedWorkPool 会先从 expiringWorkerQueue 中找还没过期的 ThreadWorker,如果没有就新建一个。
  • 在任务执行完成后,会自动调用装饰类 EventLoopWorker 的 dispose,后续会将 ThreadWorker 设置一个过期时间,并加入队列中
  • evictorService 是一个 Timer,每隔 keepAliveTime 会执行一次 evictExpiredWorks(),将已经过期的 ThreadWorker 清出队列
    • 这里说个细节,因为 CachedWorkerPool 是每隔 60 秒清理一次队列的,因此 ThreadWorker 的存活时间取决于入队的时机,如果一直没有被再次取出,其被实际清理的延迟在 60 - 120 秒之间

线程销毁释放流程

ExecutorService & RxJava Schedulers 原理

DisposeTask

static final class DisposeTask implements Disposable, Runnable, SchedulerRunnableIntrospection {

  @NonNull
  final Runnable decoratedRun;

  @NonNull
  final Worker w;

  @Nullable
  Thread runner;

  DisposeTask(@NonNull Runnable decoratedRun, @NonNull Worker w) {
    this.decoratedRun = decoratedRun;
    this.w = w;
  }

  @Override
  public void run() {
    runner = Thread.currentThread();
    try {
      decoratedRun.run();
    } finally {
      dispose();
      runner = null;
    }
  }

  @Override
  public void dispose() {
    if (runner == Thread.currentThread() && w instanceof NewThreadWorker) {
      ((NewThreadWorker)w).shutdown();
    } else {
      w.dispose();
    }
  }

  @Override
  public boolean isDisposed() {
    return w.isDisposed();
  }

  @Override
  public Runnable getWrappedRunnable() {
    return this.decoratedRun;
  }
}

EventLoopWorker

static final class EventLoopWorker extends Scheduler.Worker {
  private final CompositeDisposable tasks;
  private final CachedWorkerPool pool;
  private final ThreadWorker threadWorker;

  final AtomicBoolean once = new AtomicBoolean();

  EventLoopWorker(CachedWorkerPool pool) {
    this.pool = pool;
    this.tasks = new CompositeDisposable();
    this.threadWorker = pool.get();
  }

  @Override
  public void dispose() {
    if (once.compareAndSet(false, true)) {
      tasks.dispose();

      // releasing the pool should be the last action
      pool.release(threadWorker);
    }
  }

  @Override
  public boolean isDisposed() {
    return once.get();
  }

  @NonNull
  @Override
  public Disposable schedule(@NonNull Runnable action, long delayTime, @NonNull TimeUnit unit) {
    if (tasks.isDisposed()) {
      // don't schedule, we are unsubscribed
      return EmptyDisposable.INSTANCE;
    }

    return threadWorker.scheduleActual(action, delayTime, unit, tasks);
  }
}

CachedWorkerPool#release()

void release(ThreadWorker threadWorker) {
  // Refresh expire time before putting worker back in pool
  threadWorker.setExpirationTime(now() + keepAliveTime);

  expiringWorkerQueue.offer(threadWorker);
}
  • pool.release(threadWorker) 会将 threadWorker 设置过期时间,然后加入队列

CachedWorkerPool#evictExpiredWorkers()

void evictExpiredWorkers() {
  if (!expiringWorkerQueue.isEmpty()) {
    long currentTimestamp = now();

    for (ThreadWorker threadWorker : expiringWorkerQueue) {
      if (threadWorker.getExpirationTime() <= currentTimestamp) {
        if (expiringWorkerQueue.remove(threadWorker)) {
          allWorkers.remove(threadWorker);
        }
      } else {
        // Queue is ordered with the worker that will expire first in the beginning, so when we
        // find a non-expired worker we can stop evicting.
        break;
      }
    }
  }
}
  • allAWorkers.remove 会调用 threadWorker 的 dispose()

NewThreadWorker#dispose()

@Override
public void dispose() {
  if (!disposed) {
    disposed = true;
    executor.shutdownNow();
  }
}
  • dispose() 最终调用了 ExecuterSerivce#shutdownNow()

参考

转载自:https://juejin.cn/post/7158076549072158756
评论
请登录