阿里面经之解答 by cmershen(4)——线程池

版权声明:完整版Leetcode题解请出门左转https://github.com/cmershen1/leetcode/tree/master/docs https://blog.csdn.net/cmershen/article/details/51838385

18.什么叫线程池?如何实现线程池?

因为新建一个线程的成本比较高,所以在大型系统中使用线程池可以很好的提高性能。线程池就是在系统启动的时候先创建大量的空闲线程,当该线程的run()或call()方法执行结束后,该线程不会立即死亡,而是在线程池中等待再次唤醒。这很适合大型系统中需要大量、重复、短暂地启动线程的操作。
除此之外,线程池技术可以有效的控制并发数量,以防线程开启过多时导致jvm崩溃。

介绍如何实现线程池之前,我们先了解一下如何使用线程池,线程池有哪些功能。
Java中有一个Executors工厂类,它提供了以下静态方法:

  • newFixedThreadPool(int nThreads): 创建一个固定的线程数的线程池。
  • newWorkStealingPool(int parallelism): 给定一个并行度,创建一个保持足够线程数以支持这个并行度的线程池,并使用多个队列来减少竞争。(jdk源码中原文:Creates a thread pool that maintains enough threads to support the given parallelism level, and may use multiple queues to reduce contention. 李刚《疯狂Java讲义》对这句话的翻译简直不知所云,不知道是不是谷歌翻译的。 )
  • newFixedThreadPool(int nThreads,ThreadFactory threadFactory): newFixedThreadPool(int nThreads)方法的重载,增加了ThreadFactory对象,代表从一个线程工厂里创建线程池。
  • newSingleThreadExecutor(),创建只有一个只有单线程的线程池。
  • newSingleThreadExecutor(ThreadFactory threadFactory):上述方法的重载。
  • newCachedThreadPool():创建一个具有缓存功能的线程池。
  • newSingleThreadScheduledExecutor():创建只有一个只有单线程的线程池,它可以在指定延迟过后执行线程。
  • newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟过后执行线程。

创建了不指定延迟的线程池之后,返回的是一个ExecutorService类型的对象。
ExecutorService是个接口,它里面主要有如下抽象方法:
* void shutdown();关闭线程池,禁止新的线程提交至线程池。
* Future<?> submit(Runnable task);提交一个任务。也可以提交Callable对象。
如果创建了指定延迟的线程池之后,返回的是ScheduledExecutorService类型的对象。类似地,这个接口(继承ExecutorService)里面有如下抽象方法:
* schedule(Runnable command, long delay, TimeUnit unit);指定一个任务在unit的delay倍延迟后执行。也可以替换成Callable。
* scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit);指定一个任务在unit的delay倍延迟后执行,而且以period为周期重复执行。

那么用法就已经很明确了:先创建线程池,再调用submit将线程对象提交进线程池中,不需要提交线程了就调用shutdown()关闭线程池。

接下来我们看看jdk 1.8中是如何实现这些方法的。
先看最简单的固定线程池,它返回的是ThreadPoolExecutor对象。这个对象的构造器如下:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

入口参数分别是:corePoolSize-线程池中要保持运行的线程数,maximumPoolSize-线程池中允许的最多线程个数(默认这两个值都是相等的),keepAliveTime,unit-当线程池已满的时候若有新线程提交,则新线程在队列里最多等待的时间(默认为0),workQueue-线程在执行前放在一个阻塞队列中等待。
接下来看submit():(submit的原型在父类AbstractExecutorService.java里面)

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

RunnableFuture是Runnable和Future的封装,其实知乎里也有人问了为什么把他们两个封装起来,在这里本学妹也不想深究,反正2-3行代码的意思就是把任务封装进这么一个奇怪的类里面,然后调用execute方法执行这个任务。
再来看execute()方法,当然大家可能注意到了,它和构造器都是public的,我们亦可以直接构造ThreadPoolExecutor并调用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);
    }

Doug Lea大仙已经解释了这段代码,分成三步,如果当前线程池中正在执行的线程数比corePoolSize少,就调用addWorker方法执行这个线程,否则将其加入阻塞队列等待。这里还有一个二次检查,因为Doug Lea也说到了,有可能这时恰好有一个线程结束了或者线程池已经shutdown了,在这种情况下就不应放入阻塞队列等待,应该roll back这次操作(remove(command);)第三步如果没放进队列中去,那么我们再提交一次这个任务,这次与maximumPoolSize比,如果还没提交上去,则reject这个任务。

那么重头戏来了,接下来就是封装在最底层的这个addWorker方法,一切都装在这个里面。

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

这个方法有两个参数,第一个不用说了,第二个是一个标志位,代表比较线程该不该加入线程池是与corePoolSize比还是与maximumPoolSize比。
这个函数首先检查各种状态(具体细节真没看懂),如果满足可以加入的条件,则跳出retry循环,进入函数的第二部分。第二部分首先将要执行的线程封装到Worker类里面,并线程同步地把要执行的线程加入线程池的workers属性里面(恩,忘说了,已经在执行的线程是用一个HashSet<Worker>维护的),并置标志位workerAdded=true;,再开启此线程。

这里有的读者可能要问了,我们研究到这里,只知道了在HashSet<Worker>里面放入线程,那执行完了应该从HashSet里面取出来,怎么没看见呢?别急。ThreadPoolExecutor的第950行这个t.start();里面还有文章呢!
t.start();真正调用的是这个runWorker()方法:

  final void runWorker(Worker w) {
      Thread wt = Thread.currentThread();
      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);
      }
  }

可以观察到,task.run执行之后,会进入finally块,在最后一个finally块里面有一个processWorkerExit(w, completedAbruptly).在这个方法里面有如下片段:

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

这里程序线程同步地修改已经完成的任务数目,并把当前线程从workers集合里移除。

沿着submit()我们分析了这么多,接下来我们开始研究shutdown().

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

这个方法里面调用了四个方法,下面一一分析之。

checkShutdownAccess();方法大概是检查操作系统是否允许JVM操作进程。
advanceRunState(SHUTDOWN);方法将当前线程池的状态置为SHUTDOWN。
interruptIdleWorkers();这个方法顾名思义,就是将线程池中等待调度的线程中断。
onShutdown();该方法在这个类里面是空的,但在其子接口ScheduledThreadPoolExecutor中则用于取消处在延迟期的任务。

接下来就是ScheduledThreadPoolExecutor类了(其实我也没细看),这里面submit替换成了schedule,而schedule()的源码与submit()十分类似,只是execute()替换成了delayedExecute():

    private void delayedExecute(RunnableScheduledFuture<?> task) {
        if (isShutdown())
            reject(task);
        else {
            super.getQueue().add(task);
            if (isShutdown() &&
                !canRunInCurrentRunState(task.isPeriodic()) &&
                remove(task))
                task.cancel(false);
            else
                ensurePrestart();
        }
    }

我怀疑这个包并不是Doug Lea开发的,这个delayedExecute变得迷之简单,大概意思就是将任务加入队列,如果不满足一定条件则回滚,否则并确保可以执行即可……
shutdown()不用说了,直接一句super.shutdown();了事………………

本期就写了一道题,因为内容展开了很多,所以本学妹脑子已经炸了。。。。。。。。。。。。。。

展开阅读全文

没有更多推荐了,返回首页