JAVA基础:线程池ThreadPoolExecutor及Future原理解析

线程池的作用

  1. 为了降低在处理短时间任务创建与销毁线程的代价。
  2. 控制线程的数量,过多的线程会带来调度的开销以及资源的占用。
  3. 一定程度上提高程序的稳定性和可维护性,防止出现意外创建过多线程的情况导致资源占用满了出现GC问题甚至导致程序崩溃。

ThreadPoolExecutor实现原理

构造函数

首先我们来看一下ThreadPoolExecutor类的构造函数,看看它需要哪些参数,通过对这些参数的讲解,我们再来分析它的原理以及是如何实现的。这些参数不是每个都需要传入,但是需要理解其中的含义。


/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        //构造函数代码代码省略
    }
  1. corePoolSize。核心线程数,主要工作线程,就算线程是空闲的也不会销毁,除非设置了allowCoreThreadTimeOut参数。
  2. maximumPoolSize,最大线程数量。当任务队列满了的时候,会启动新的线程,该线程空闲keepAliveTime时间后会被销毁。
  3. keepAliveTime,就是大于corePoolSize小于maximumPoolSize的线程空闲这么多时间后会被销毁。
  4. keepAliveTime的时间单位。
  5. 当corePoolSize个线程都在工作时,这个时候如果来了新的任务无法立即执行则会丢到这个队列中等待,等到有线程空闲时会从该队列中获取任务。当该队列满了的时候仍然有新任务来时会拒绝。
  6. threadFactory是用来创建线程的,每当线程池需要创建一个新的线程时,会使用该工厂对象来创建新的线程。
  7. handler当拒绝新的任务时,会执行的回调。默认实现是抛出一个RejectedExecutionException异常。

线程状态

ctl又存储了worker数量也存储了线程池的状态。 从
Running-> ShutDown -> Stop-> TiDying->TERMINATE

 *   RUNNING:  Accept new tasks and process queued tasks
 *   SHUTDOWN: Don't accept new tasks, but process queued tasks
 *   STOP:     Don't accept new tasks, don't process queued tasks,
 *             and interrupt in-progress tasks
 *   TIDYING:  All tasks have terminated, workerCount is zero,
 *             the thread transitioning to state TIDYING
 *             will run the terminated() hook method
 *   TERMINATED: terminated() has completed
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

核心方法

execute

首先当小于corePoolSize时,addWorker,如果可以往队列里还可以塞的话,就塞进去,塞不进去就新增worker,失败就拒绝。
所以这里有2个问题:
1.新增的worker如何执行线程
2.worker如何取队列里面拿线程执行

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // 小于corePoolSize直接新增worker
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // 否则offer塞入队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            // 新增worker后如果不是运行状态则拒绝
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // 继续新增worker,新增失败则拒绝
        else if (!addWorker(command, false))
            reject(command);
    }

上面代码解释了corePoolSize的原理,它影响到addWorker的调用。带着问题1
我们来看一下addWorker方法做了什么。

 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)) {
                        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();
                }
                // worker新增完成后在这里启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }

。将Runnable封装到一个Worker里,然后再封装到一个Thread对象里,然后执行t.start()启动就执行了Worker.run()方法。这里解决了第一个问题,那么第二个问题呢?我们再看看Worker的代码。

Worker的执行

 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
        //一旦一个worker启动以后,就不停的调用getTask()来从队列中拿任务执行
            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);
        }
    }

这里worker实现了Runnable接口,它本身也是一个可以执行的任务,封装了thread,同时调用runWorker方法执行任务。并且当任务执行完成后循环从队列中获取任务。这里解释了第二个问题。

submit

众所周知,我们可以执行submit方法来向线程池提交任务,然后通过返回的Future对象来获得任务执行的状态,通过future.get()方法来阻塞等待其完成任务。那么这个流程是怎么实现的呢。这里有几个问题:

  1. Future的实现原理,它是如何保存任务状态的,以及如何阻塞和唤醒的。
  2. Future的get()方法阻塞时,如果线程被中断了会怎么样。

首先我们来看一下submit方法,它是AbstractExecutorService类的一个方法。它将我们传入的task对象封装到了一个RunnableFuture对象中,实际是一个FutureTask对象,它实现了Future和Funnable接口。

public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }

sys在这里插入图片描述
所以如上图所示,我们的任务被封装在future对象中并交给了线程池,然后被封装到Worker中进行执行。通过对象组合的方式,为我们的runnable增加了很多功能。下面我们来分析一下FutureTask的原理。

FutureTask原理

Future的状态

  • Possible state transitions:
    • NEW -> COMPLETING -> NORMAL
    • NEW -> COMPLETING -> EXCEPTIONAL
    • NEW -> CANCELLED
    • NEW -> INTERRUPTING -> INTERRUPTED
      NEW代表刚刚创建,COMPLETING代表任务执行完成,等待设置结果值。NORMAL代表正常执行,设置结果,EXCEPTIONAL代表执行过程抛出异常,设置了异常对象。
Future的执行
 public void run() {
        if (state != NEW ||
        // 把当前线程设置到runner字段中,注意runner字段用volatile修饰了,如果任务已经启动了,则直接返回。防止多个线程同时启动冲突问题。
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            //为什么这里要先获取c再执行c.call()而不是直接用对象的属性callable来执行?是不是和线程安全相关,防止在判断了callable!=null之后callable又变了?
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    setException(ex);
                }
                if (ran)
                  	//设置结果值,并改变future的状态,同时唤醒等待的线程
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }
    
protected void set(V v) {
		
       if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
           outcome = v;
           UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
           finishCompletion();
       }
   }
//这里是释放阻塞线程的核心方法,释放等待节点WaitNode
private void finishCompletion() {
      // assert state > COMPLETING;
      for (WaitNode q; (q = waiters) != null;) {
          if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
              for (;;) {
                  Thread t = q.thread;
                  if (t != null) {
                      q.thread = null;
                      LockSupport.unpark(t);
                  }
                  WaitNode next = q.next;
                  if (next == null)
                      break;
                  q.next = null; // unlink to help gc
                  q = next;
              }
              break;
          }
      }
      done();
      callable = null;        // to reduce footprint
  }

我们总结一下,FutureTask的执行是先执行run()方法,执行了其封装的callable对象,即我们要执行的任务,然后将任务执行结果设置到result中,同时唤醒等待线程。在了解如何唤醒等待线程之前,我们先看一下线程是如何阻塞的。

FutureTask阻塞

当我们调用future.get时,主要逻辑在awaitDone方法中。

 private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        final long deadline = timed ? System.nanoTime() + nanos : 0L;
        WaitNode q = null;
        boolean queued = false;
        for (;;) {
        	
            if (Thread.interrupted()) {
            //如果线程的状态是中断的,则移除等待的节点
                removeWaiter(q);
                throw new InterruptedException();
            }

            int s = state;
           
            if (s > COMPLETING) {
            // 如果大于处理中,那么可能已经处理完成或者处理失败,这里会返回线程状态交给report方法
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING) // cannot time out yet
             //如果是处理中,说明应该马上就会完成了,就等一会
                Thread.yield();
            else if (q == null)
            // 初始化等待节点
                q = new WaitNode();
            else if (!queued)
            // 初始化后如果没有加入队列,则加入队列。将新创建的节点的next指向原有的节点,并将waiters属性设置为新的节点。q.next = waiters的表达式的值为waiters。赋值的同时返回被赋的值。
                queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                                     q.next = waiters, q);
            else if (timed) {
                nanos = deadline - System.nanoTime();
                if (nanos <= 0L) {
                    removeWaiter(q);
                    return state;
                }
                // 阻塞一定时长
                LockSupport.parkNanos(this, nanos);
            }
            else
               //阻塞线程
                LockSupport.park(this);
        }
    }
    
static final class WaitNode {
       volatile Thread thread;
       volatile WaitNode next;
       WaitNode() { thread = Thread.currentThread(); }
  }

所以从以上代码我们可以看出线程等待时会创建一个节点,节点中记录了调用get()方法的当前线程,并将节点入队。入队后通过LockSupport.park阻塞线程。
知道了如何阻塞线程的,那么再来看唤醒线程就很清晰了。

FutureTask唤醒
//这里是释放阻塞线程的核心方法,释放等待节点WaitNode
private void finishCompletion() {
      // assert state > COMPLETING;
      for (WaitNode q; (q = waiters) != null;) {
      //这里用循环和cas操作来保证同时只有一个线程可以成功执行if语句中的操作。
          if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
          // 将 waiters设置为null,代表已经有线程在处理唤醒了,同时帮助gc
              for (;;) {
              //循环唤醒所有等待节点中的线程
                  Thread t = q.thread;
                  if (t != null) {
                      q.thread = null;
                      LockSupport.unpark(t);
                  }
                  WaitNode next = q.next;
                  if (next == null)
                      break;
                  q.next = null; // unlink to help gc
                  q = next;
              }
              break;
          }
      }
      done();
      // 这里设置为null还不是很理解,是为了帮助gc吧。降低对环境的影响。
      callable = null;        // to reduce footprint
  }

从上面的代码我们可以看到,future的阻塞和唤醒,是通过FutureTask中的WaitNode链表来实现的。future.get时入队挂起,任务完成时遍历队列唤醒。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值