java中的所说的线程池,一般都是围绕着 ThreadPoolExecutor 来展开的。其他的实现基本都是基于它,或者模仿它的。所以只要理解 ThreadPoolExecutor, 就相当于完全理解了线程池的精髓。

  其实要理解一个东西,一般地,我们最好是要抱着自己的疑问或者理解去的。否则,往往收获甚微。

  理解 ThreadPoolExecutor, 我们可以先理解一个线程池的意义: 本质上是提供预先定义好的n个线程,供调用方直接运行任务的一个工具。

线程池解决的问题:

  1. 提高任务执行的响应速度,降低资源消耗。任务执行时,直接立即使用线程池提供的线程运行,避免了临时创建线程的CPU/内存开销,达到快速响应的效果。

  2. 提高线程的可管理性。线程总数可预知,避免用户主动创建无限多线程导致死机风险,还可以进行线程统一的分配、调优和监控。

  3. 避免对资源的过度使用。在超出预期的请求任务情况,响应策略可控。

线程池提供的核心接口:

  要想使用线程池,自然是要理解其接口的。一般我们使用 ExecotorService 进行线程池的调用。然而,我们并不针对初学者。

  整体的接口如下:

  我们就挑几个常用接口探讨下:

    submit(Runnable task): 提交一个无需返回结果的任务。
    submit(Callable<T> task): 提交一个有返回结果的任务。
    invokeAll(Collection<? extends Callable<T>> tasks, long, TimeUnit): 同时执行n个任务并返回结果列表。
    shutdown(): 关闭线程程池。
    awaitTermination(long timeout, TimeUnit unit): 等待关闭结果,最长不超过timeout时间。

以上是ThreadPoolExector 提供的特性,针对以上特性。

我们应该要有自己的几个实现思路或疑问:

  1. 线程池如何接受任务?

  2. 线程如何运行任务?

  3. 线程池如何关闭?

接下来,就让我们带着疑问去看实现吧。

ThreadPoolExecutor 核心实现原理

1. 线程池的处理流程

  我们首先重点要看的是,如何执行提交的任务。我可以通过下图来看看。

  总结描述下就是:

    1. 判断核心线程池是否已满,如果不是,则创建线程执行任务
    2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中
    3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务
    4. 如果线程池也满了,则按照拒绝策略对任务进行处理

  另外,我们来看一下 ThreadPoolExecutor 的构造方法,因为这里会体现出每个属性的含义。

  1. /**
  2. * Creates a new {@code ThreadPoolExecutor} with the given initial
  3. * parameters.
  4. *
  5. * @param corePoolSize the number of threads to keep in the pool, even
  6. * if they are idle, unless {@code allowCoreThreadTimeOut} is set
  7. * @param maximumPoolSize the maximum number of threads to allow in the
  8. * pool
  9. * @param keepAliveTime when the number of threads is greater than
  10. * the core, this is the maximum time that excess idle threads
  11. * will wait for new tasks before terminating.
  12. * @param unit the time unit for the {@code keepAliveTime} argument
  13. * @param workQueue the queue to use for holding tasks before they are
  14. * executed. This queue will hold only the {@code Runnable}
  15. * tasks submitted by the {@code execute} method.
  16. * @param threadFactory the factory to use when the executor
  17. * creates a new thread
  18. * @param handler the handler to use when execution is blocked
  19. * because the thread bounds and queue capacities are reached
  20. * @throws IllegalArgumentException if one of the following holds:<br>
  21. * {@code corePoolSize < 0}<br>
  22. * {@code keepAliveTime < 0}<br>
  23. * {@code maximumPoolSize <= 0}<br>
  24. * {@code maximumPoolSize < corePoolSize}
  25. * @throws NullPointerException if {@code workQueue}
  26. * or {@code threadFactory} or {@code handler} is null
  27. */
  28. public ThreadPoolExecutor(int corePoolSize,
  29. int maximumPoolSize,
  30. long keepAliveTime,
  31. TimeUnit unit,
  32. BlockingQueue<Runnable> workQueue,
  33. ThreadFactory threadFactory,
  34. RejectedExecutionHandler handler) {
  35. if (corePoolSize < 0 ||
  36. maximumPoolSize <= 0 ||
  37. maximumPoolSize < corePoolSize ||
  38. keepAliveTime < 0)
  39. throw new IllegalArgumentException();
  40. if (workQueue == null || threadFactory == null || handler == null)
  41. throw new NullPointerException();
  42. this.corePoolSize = corePoolSize;
  43. this.maximumPoolSize = maximumPoolSize;
  44. this.workQueue = workQueue;
  45. this.keepAliveTime = unit.toNanos(keepAliveTime);
  46. this.threadFactory = threadFactory;
  47. this.handler = handler;
  48. }

  从构造方法可以看出 ThreadPoolExecutor 的主要参数 7 个,在其注释上也有说明功能,咱们翻译下每个参数的功能:

  1. corePoolSize: 线程池核心线程数(平时保留的线程数),使用时机: 在初始时刻,每次请求进来都会创建一个线程直到达到该size
  2. maximumPoolSize: 线程池最大线程数,使用时机: workQueue都放不下时,启动新线程,直到最大线程数,此时到达线程池的极限
  3. keepAliveTime/unit: 超出corePoolSize数量的线程的保留时间,unit为时间单位
  4. workQueue: 阻塞队列,当核心线程数达到或者超出后,会先尝试将任务放入该队列由各线程自行消费;
  5. ArrayBlockingQueue: 构造函数一定要传大小
  6. LinkedBlockingQueue: 构造函数不传大小会默认为65536Integer.MAX_VALUE ),当大量请求任务时,容易造成 内存耗尽。
  7. SynchronousQueue: 同步队列,一个没有存储空间的阻塞队列 ,将任务同步交付给工作线程。
  8. PriorityBlockingQueue: 优先队列
  9. threadFactory:线程工厂,用于线程需要创建时,调用其newThread()生产新线程使用
  10. handler: 饱和策略,当队列已放不下任务,且创建的线程已达到 maximum 后,则不能再处理任务,直接将任务交给饱和策略
  11. AbortPolicy: 直接抛弃(默认)
  12. CallerRunsPolicy: 用调用者的线程执行任务
  13. DiscardOldestPolicy: 抛弃队列中最久的任务
  14. DiscardPolicy: 抛弃当前任务

2. submit 流程详解

  当调用 submit 方法,就是向线程池中提交一个任务,处理流程如步骤1所示。但是我们需要更深入理解。

  submit 方法是定义在 AbstractExecutorService 中,最终调用 ThreadPoolExecutor 的 execute 方法,即是模板方法模式的应用。

  1. // java.util.concurrent.AbstractExecutorService#submit(java.lang.Runnable, T)
  2. /**
  3. * @throws RejectedExecutionException {@inheritDoc}
  4. * @throws NullPointerException {@inheritDoc}
  5. */
  6. public <T> Future<T> submit(Runnable task, T result) {
  7. if (task == null) throw new NullPointerException();
  8. // 封装任务和返回结果为 RunnableFuture, 统一交由具体的子类执行
  9. RunnableFuture<T> ftask = newTaskFor(task, result);
  10. // execute 将会调用 ThreadPoolExecutor 的实现,是我们讨论的重要核心
  11. execute(ftask);
  12. return ftask;
  13. }
  14. // FutureTask 是个重要的线程池组件,它承载了具体的任务执行流
  15. /**
  16. * Returns a {@code RunnableFuture} for the given runnable and default
  17. * value.
  18. *
  19. * @param runnable the runnable task being wrapped
  20. * @param value the default value for the returned future
  21. * @param <T> the type of the given value
  22. * @return a {@code RunnableFuture} which, when run, will run the
  23. * underlying runnable and which, as a {@code Future}, will yield
  24. * the given value as its result and provide for cancellation of
  25. * the underlying task
  26. * @since 1.6
  27. */
  28. protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
  29. return new FutureTask<T>(runnable, value);
  30. }
  31.  
  32. // ThreadPoolExecutor 的任务提交过程
  33. // java.util.concurrent.ThreadPoolExecutor#execute
  34. /**
  35. * Executes the given task sometime in the future. The task
  36. * may execute in a new thread or in an existing pooled thread.
  37. *
  38. * If the task cannot be submitted for execution, either because this
  39. * executor has been shutdown or because its capacity has been reached,
  40. * the task is handled by the current {@code RejectedExecutionHandler}.
  41. *
  42. * @param command the task to execute
  43. * @throws RejectedExecutionException at discretion of
  44. * {@code RejectedExecutionHandler}, if the task
  45. * cannot be accepted for execution
  46. * @throws NullPointerException if {@code command} is null
  47. */
  48. public void execute(Runnable command) {
  49. if (command == null)
  50. throw new NullPointerException();
  51. /*
  52. * Proceed in 3 steps:
  53. *
  54. * 1. If fewer than corePoolSize threads are running, try to
  55. * start a new thread with the given command as its first
  56. * task. The call to addWorker atomically checks runState and
  57. * workerCount, and so prevents false alarms that would add
  58. * threads when it shouldn't, by returning false.
  59. *
  60. * 2. If a task can be successfully queued, then we still need
  61. * to double-check whether we should have added a thread
  62. * (because existing ones died since last checking) or that
  63. * the pool shut down since entry into this method. So we
  64. * recheck state and if necessary roll back the enqueuing if
  65. * stopped, or start a new thread if there are none.
  66. *
  67. * 3. If we cannot queue task, then we try to add a new
  68. * thread. If it fails, we know we are shut down or saturated
  69. * and so reject the task.
  70. */
  71. // ctl 是一个重要的控制全局状态的数据结构,定义为一个线程安全的 AtomicInteger
  72. // ctl = new AtomicInteger(ctlOf(RUNNING, 0));
  73. int c = ctl.get();
  74. // 当还没有达到核心线程池的数量时,直接添加1个新线程,然后让其执行任务即可
  75. if (workerCountOf(c) < corePoolSize) {
  76. // 2.1. 添加新线程,且执行command任务
  77. // 添加成功,即不需要后续操作了,添加失败,则说明外部环境变化了
  78. if (addWorker(command, true))
  79. return;
  80. c = ctl.get();
  81. }
  82. // 当核心线程达到后,则尝试添加到阻塞队列中,具体添加方法由阻塞队列实现
  83. // isRunning => c < SHUTDOWN;
  84. if (isRunning(c) && workQueue.offer(command)) {
  85. int recheck = ctl.get();
  86. // 2.2. 添加队列成功后,还要再次检测线程池的运行状态,决定启动线程或者状态过期
  87. // 2.2.1. 当线程池已关闭,则将刚刚添加的任务移除,走reject策略
  88. if (! isRunning(recheck) && remove(command))
  89. reject(command);
  90. // 2.2.2. 当一个worker都没有时,则添加worker
  91. else if (workerCountOf(recheck) == 0)
  92. addWorker(null, false);
  93. }
  94. // 当队列满后,则直接再创建新的线程运行,如果不能再创建线程了,则 reject
  95. else if (!addWorker(command, false))
  96. // 2.3. 拒绝策略处理
  97. reject(command);
  98. }

  通过上面这一小段代码,我们就已经完整地看到了。通过一个 ctl 变量进行全局状态控制,从而保证了线程安全性。整个框架并没有使用锁,但是却是线程安全的。

  整段代码刚好完整描述了线程池的执行流程:

    1. 判断核心线程池是否已满,如果不是,则创建线程执行任务;
    2. 如果核心线程池满了,判断队列是否满了,如果队列没满,将任务放在队列中;
    3. 如果队列满了,则判断线程池是否已满,如果没满,创建线程执行任务;
    4. 如果线程池也满了,则按照拒绝策略对任务进行处理;

2.1. 添加新的worker

  一个worker,即是一个工作线程。

  1. /**
  2. * Checks if a new worker can be added with respect to current
  3. * pool state and the given bound (either core or maximum). If so,
  4. * the worker count is adjusted accordingly, and, if possible, a
  5. * new worker is created and started, running firstTask as its
  6. * first task. This method returns false if the pool is stopped or
  7. * eligible to shut down. It also returns false if the thread
  8. * factory fails to create a thread when asked. If the thread
  9. * creation fails, either due to the thread factory returning
  10. * null, or due to an exception (typically OutOfMemoryError in
  11. * Thread.start()), we roll back cleanly.
  12. *
  13. * @param firstTask the task the new thread should run first (or
  14. * null if none). Workers are created with an initial first task
  15. * (in method execute()) to bypass queuing when there are fewer
  16. * than corePoolSize threads (in which case we always start one),
  17. * or when the queue is full (in which case we must bypass queue).
  18. * Initially idle threads are usually created via
  19. * prestartCoreThread or to replace other dying workers.
  20. *
  21. * @param core if true use corePoolSize as bound, else
  22. * maximumPoolSize. (A boolean indicator is used here rather than a
  23. * value to ensure reads of fresh values after checking other pool
  24. * state).
  25. * @return true if successful
  26. */
  27. private boolean addWorker(Runnable firstTask, boolean core) {
  28. // 为确保线程安全,进行CAS反复重试
  29. retry:
  30. for (;;) {
  31. int c = ctl.get();
  32. // 获取runState , c 的高位存储
  33. // c & ~CAPACITY;
  34. int rs = runStateOf(c);
  35.  
  36. // Check if queue empty only if necessary.
  37. // 已经shutdown, firstTask 为空的添加并不会成功
  38. if (rs >= SHUTDOWN &&
  39. ! (rs == SHUTDOWN &&
  40. firstTask == null &&
  41. ! workQueue.isEmpty()))
  42. return false;
  43.  
  44. for (;;) {
  45. int wc = workerCountOf(c);
  46. // 如果超出最大允许创建的线程数,则直接失败
  47. if (wc >= CAPACITY ||
  48. wc >= (core ? corePoolSize : maximumPoolSize))
  49. return false;
  50. // CAS 更新worker+1数,成功则说明占位成功退出retry,后续的添加操作将是安全的,失败则说明已有其他线程变更该值
  51. if (compareAndIncrementWorkerCount(c))
  52. break retry;
  53. c = ctl.get(); // Re-read ctl
  54. // runState 变更,则退出到 retry 重新循环
  55. if (runStateOf(c) != rs)
  56. continue retry;
  57. // else CAS failed due to workerCount change; retry inner loop
  58. }
  59. }
  60. // 以下为添加 worker 过程
  61. boolean workerStarted = false;
  62. boolean workerAdded = false;
  63. Worker w = null;
  64. try {
  65. // 使用 Worker 封闭 firstTask 任务,后续运行将由 Worker 接管
  66. w = new Worker(firstTask);
  67. final Thread t = w.thread;
  68. if (t != null) {
  69. final ReentrantLock mainLock = this.mainLock;
  70. // 添加 worker 的过程,需要保证线程安全
  71. mainLock.lock();
  72. try {
  73. // Recheck while holding lock.
  74. // Back out on ThreadFactory failure or if
  75. // shut down before lock acquired.
  76. int rs = runStateOf(ctl.get());
  77. // SHUTDOWN 情况下还是会创建 Worker, 但是后续检测将会失败
  78. if (rs < SHUTDOWN ||
  79. (rs == SHUTDOWN && firstTask == null)) {
  80. // 既然是新添加的线程,就不应该是 alive 状态
  81. if (t.isAlive()) // precheck that t is startable
  82. throw new IllegalThreadStateException();
  83. // workers 只是一个工作线程的容器,使用 HashSet 承载
  84. // private final HashSet<Worker> workers = new HashSet<Worker>();
  85. workers.add(w);
  86. int s = workers.size();
  87. // 维护一个全局达到过的最大线程数计数器
  88. if (s > largestPoolSize)
  89. largestPoolSize = s;
  90. workerAdded = true;
  91. }
  92. } finally {
  93. mainLock.unlock();
  94. }
  95. // worker 添加成功后,进行将worker启起来,里面应该是有一个 死循环,一直在获取任务
  96. // 不然怎么运行添加到队列里的任务呢?
  97. if (workerAdded) {
  98. t.start();
  99. workerStarted = true;
  100. }
  101. }
  102. } finally {
  103. // 如果任务启动失败,则必须进行清理,返回失败
  104. if (! workerStarted)
  105. addWorkerFailed(w);
  106. }
  107. return workerStarted;
  108. }
  109. // 大概添加 worker 的框架明白了,重点对象是 Worker, 我们稍后再讲
  110. // 现在先来看看,添加失败的情况,如何进行
  111. /**
  112. * Rolls back the worker thread creation.
  113. * - removes worker from workers, if present
  114. * - decrements worker count
  115. * - rechecks for termination, in case the existence of this
  116. * worker was holding up termination
  117. */
  118. private void addWorkerFailed(Worker w) {
  119. final ReentrantLock mainLock = this.mainLock;
  120. mainLock.lock();
  121. try {
  122. if (w != null)
  123. workers.remove(w);
  124. // ctl 中的 workerCount - 1 , CAS 实现
  125. decrementWorkerCount();
  126. // 尝试处理空闲线程
  127. tryTerminate();
  128. } finally {
  129. mainLock.unlock();
  130. }
  131. }
  132. /**
  133. * Decrements the workerCount field of ctl. This is called only on
  134. * abrupt termination of a thread (see processWorkerExit). Other
  135. * decrements are performed within getTask.
  136. */
  137. private void decrementWorkerCount() {
  138. do {} while (! compareAndDecrementWorkerCount(ctl.get()));
  139. }
  140. // 停止可能启动的 worker
  141. /**
  142. * Transitions to TERMINATED state if either (SHUTDOWN and pool
  143. * and queue empty) or (STOP and pool empty). If otherwise
  144. * eligible to terminate but workerCount is nonzero, interrupts an
  145. * idle worker to ensure that shutdown signals propagate. This
  146. * method must be called following any action that might make
  147. * termination possible -- reducing worker count or removing tasks
  148. * from the queue during shutdown. The method is non-private to
  149. * allow access from ScheduledThreadPoolExecutor.
  150. */
  151. final void tryTerminate() {
  152. for (;;) {
  153. int c = ctl.get();
  154. // 线程池正在运行、正在清理、已关闭但队列还未处理完,都不会进行 terminate 操作
  155. if (isRunning(c) ||
  156. // c >= TIDYING
  157. runStateAtLeast(c, TIDYING) ||
  158. (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
  159. return;
  160. if (workerCountOf(c) != 0) { // Eligible to terminate
  161. // 停止线程的两个方式之一,只中断一个 worker
  162. interruptIdleWorkers(ONLY_ONE);
  163. return;
  164. }
  165. // 以下为整个线程池的后置操作
  166. final ReentrantLock mainLock = this.mainLock;
  167. mainLock.lock();
  168. try {
  169. // 设置正在清理标识
  170. if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
  171. try {
  172. // 线程池已终止的钩子方法,默认实现为空
  173. terminated();
  174. } finally {
  175. ctl.set(ctlOf(TERMINATED, 0));
  176. // 此处 termination 为唤醒等待关闭的线程
  177. termination.signalAll();
  178. }
  179. return;
  180. }
  181. } finally {
  182. mainLock.unlock();
  183. }
  184. // else retry on failed CAS
  185. }
  186. }
  187. /**
  188. * Interrupts threads that might be waiting for tasks (as
  189. * indicated by not being locked) so they can check for
  190. * termination or configuration changes. Ignores
  191. * SecurityExceptions (in which case some threads may remain
  192. * uninterrupted).
  193. *
  194. * @param onlyOne If true, interrupt at most one worker. This is
  195. * called only from tryTerminate when termination is otherwise
  196. * enabled but there are still other workers. In this case, at
  197. * most one waiting worker is interrupted to propagate shutdown
  198. * signals in case all threads are currently waiting.
  199. * Interrupting any arbitrary thread ensures that newly arriving
  200. * workers since shutdown began will also eventually exit.
  201. * To guarantee eventual termination, it suffices to always
  202. * interrupt only one idle worker, but shutdown() interrupts all
  203. * idle workers so that redundant workers exit promptly, not
  204. * waiting for a straggler task to finish.
  205. */
  206. private void interruptIdleWorkers(boolean onlyOne) {
  207. final ReentrantLock mainLock = this.mainLock;
  208. mainLock.lock();
  209. try {
  210. // 迭代所有 worker
  211. for (Worker w : workers) {
  212. Thread t = w.thread;
  213. // 获取到 worker 的锁之后,再进行 interrupt
  214. if (!t.isInterrupted() && w.tryLock()) {
  215. try {
  216. t.interrupt();
  217. } catch (SecurityException ignore) {
  218. } finally {
  219. w.unlock();
  220. }
  221. }
  222. // 只中断一个 worker, 立即返回, 不保证 interrupt 成功
  223. if (onlyOne)
  224. break;
  225. }
  226. } finally {
  227. mainLock.unlock();
  228. }
  229. }

2.2. 当添加队列成功后,发现线程池状态变更,需要进行移除队列操作

  1. /**
  2. * Removes this task from the executor's internal queue if it is
  3. * present, thus causing it not to be run if it has not already
  4. * started.
  5. *
  6. * <p>This method may be useful as one part of a cancellation
  7. * scheme. It may fail to remove tasks that have been converted
  8. * into other forms before being placed on the internal queue. For
  9. * example, a task entered using {@code submit} might be
  10. * converted into a form that maintains {@code Future} status.
  11. * However, in such cases, method {@link #purge} may be used to
  12. * remove those Futures that have been cancelled.
  13. *
  14. * @param task the task to remove
  15. * @return {@code true} if the task was removed
  16. */
  17. public boolean remove(Runnable task) {
  18. // 此移除不一定能成功
  19. boolean removed = workQueue.remove(task);
  20. // 上面已经看过,它会尝试停止一个 worker 线程
  21. tryTerminate(); // In case SHUTDOWN and now empty
  22. return removed;
  23. }

3. 添加失败进行执行拒绝策略

  1. /**
  2. * Invokes the rejected execution handler for the given command.
  3. * Package-protected for use by ScheduledThreadPoolExecutor.
  4. */
  5. final void reject(Runnable command) {
  6. // 拒绝策略是在构造方法时传入的,默认为 RejectedExecutionHandler
  7. // 即用户只需实现 rejectedExecution 方法,即可以自定义拒绝策略了
  8. handler.rejectedExecution(command, this);
  9. }

4. Worker 的工作机制

  从上面的实现中,我们可以看到,主要是对 Worker 的添加和 workQueue 的添加,所以具体的工作是由谁完成呢?自然就是 Worker 了。

  1. // Worker 的构造方法,主要是接受一个 task, 可以为 null, 如果非null, 将在不久的将来被执行
  2. // private final class Worker extends AbstractQueuedSynchronizer implements Runnable
  3. /**
  4. * Creates with given first task and thread from ThreadFactory.
  5. * @param firstTask the first task (null if none)
  6. */
  7. Worker(Runnable firstTask) {
  8. setState(-1); // inhibit interrupts until runWorker
  9. this.firstTask = firstTask;
  10. // 将 Worker 自身当作一个 任务,绑定到 worker.thread 中
  11. // thread 启动时,worker 就启动了
  12. this.thread = getThreadFactory().newThread(this);
  13. }
  14. // Worker 的主要工作实现,通过一个循环扫描实现
  15. /** Delegates main run loop to outer runWorker */
  16. public void run() {
  17. // 调用 ThreadPoolExecutor 外部实现的 runWorker 方法
  18. runWorker(this);
  19. }
  20.  
  21. /**
  22. * Main worker run loop. Repeatedly gets tasks from queue and
  23. * executes them, while coping with a number of issues:
  24. *
  25. * 1. We may start out with an initial task, in which case we
  26. * don't need to get the first one. Otherwise, as long as pool is
  27. * running, we get tasks from getTask. If it returns null then the
  28. * worker exits due to changed pool state or configuration
  29. * parameters. Other exits result from exception throws in
  30. * external code, in which case completedAbruptly holds, which
  31. * usually leads processWorkerExit to replace this thread.
  32. *
  33. * 2. Before running any task, the lock is acquired to prevent
  34. * other pool interrupts while the task is executing, and then we
  35. * ensure that unless pool is stopping, this thread does not have
  36. * its interrupt set.
  37. *
  38. * 3. Each task run is preceded by a call to beforeExecute, which
  39. * might throw an exception, in which case we cause thread to die
  40. * (breaking loop with completedAbruptly true) without processing
  41. * the task.
  42. *
  43. * 4. Assuming beforeExecute completes normally, we run the task,
  44. * gathering any of its thrown exceptions to send to afterExecute.
  45. * We separately handle RuntimeException, Error (both of which the
  46. * specs guarantee that we trap) and arbitrary Throwables.
  47. * Because we cannot rethrow Throwables within Runnable.run, we
  48. * wrap them within Errors on the way out (to the thread's
  49. * UncaughtExceptionHandler). Any thrown exception also
  50. * conservatively causes thread to die.
  51. *
  52. * 5. After task.run completes, we call afterExecute, which may
  53. * also throw an exception, which will also cause thread to
  54. * die. According to JLS Sec 14.20, this exception is the one that
  55. * will be in effect even if task.run throws.
  56. *
  57. * The net effect of the exception mechanics is that afterExecute
  58. * and the thread's UncaughtExceptionHandler have as accurate
  59. * information as we can provide about any problems encountered by
  60. * user code.
  61. *
  62. * @param w the worker
  63. */
  64. final void runWorker(Worker w) {
  65. Thread wt = Thread.currentThread();
  66. Runnable task = w.firstTask;
  67. w.firstTask = null;
  68. w.unlock(); // allow interrupts
  69. boolean completedAbruptly = true;
  70. try {
  71. // 不停地从 workQueue 中获取任务,然后执行,就是这么个逻辑
  72. // getTask() 会阻塞式获取,所以 Worker 往往不会立即退出
  73. while (task != null || (task = getTask()) != null) {
  74. // 执行过程中是不允许并发的,即同时只能一个 task 在运行,此时也不允许进行 interrupt
  75. w.lock();
  76. // If pool is stopping, ensure thread is interrupted;
  77. // if not, ensure thread is not interrupted. This
  78. // requires a recheck in second case to deal with
  79. // shutdownNow race while clearing interrupt
  80. // 检测是否已被线程池是否停止 或者当前 worker 被中断
  81. // STOP = 1 << COUNT_BITS;
  82. if ((runStateAtLeast(ctl.get(), STOP) ||
  83. (Thread.interrupted() &&
  84. runStateAtLeast(ctl.get(), STOP))) &&
  85. !wt.isInterrupted())
  86. // 中断信息传递
  87. wt.interrupt();
  88. try {
  89. // 任务开始前 切点,默认为空执行
  90. beforeExecute(wt, task);
  91. Throwable thrown = null;
  92. try {
  93. // 直接调用任务的run方法, 具体的返回结果,会被 FutureTask 封装到 某个变量中
  94. // 可以参考以前的文章 (FutureTask是怎样获取到异步执行结果的? https://www.cnblogs.com/yougewe/p/11666284.html)
  95. task.run();
  96. } catch (RuntimeException x) {
  97. thrown = x; throw x;
  98. } catch (Error x) {
  99. thrown = x; throw x;
  100. } catch (Throwable x) {
  101. thrown = x; throw new Error(x);
  102. } finally {
  103. // 任务开始后 切点,默认为空执行
  104. afterExecute(task, thrown);
  105. }
  106. } finally {
  107. task = null;
  108. w.completedTasks++;
  109. w.unlock();
  110. }
  111. }
  112. // 正常退出,有必要的话,可能重新将 Worker 添加进来
  113. completedAbruptly = false;
  114. } finally {
  115. // 处理退出后下一步操作,可能重新添加 Worker
  116. processWorkerExit(w, completedAbruptly);
  117. }
  118. }
  119.  
  120. /**
  121. * Performs cleanup and bookkeeping for a dying worker. Called
  122. * only from worker threads. Unless completedAbruptly is set,
  123. * assumes that workerCount has already been adjusted to account
  124. * for exit. This method removes thread from worker set, and
  125. * possibly terminates the pool or replaces the worker if either
  126. * it exited due to user task exception or if fewer than
  127. * corePoolSize workers are running or queue is non-empty but
  128. * there are no workers.
  129. *
  130. * @param w the worker
  131. * @param completedAbruptly if the worker died due to user exception
  132. */
  133. private void processWorkerExit(Worker w, boolean completedAbruptly) {
  134. if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
  135. decrementWorkerCount();
  136.  
  137. final ReentrantLock mainLock = this.mainLock;
  138. mainLock.lock();
  139. try {
  140. completedTaskCount += w.completedTasks;
  141. workers.remove(w);
  142. } finally {
  143. mainLock.unlock();
  144. }
  145.  
  146. tryTerminate();
  147.  
  148. int c = ctl.get();
  149. if (runStateLessThan(c, STOP)) {
  150. // 在 Worker 正常退出的情况下,检查是否超时导致,维持最小线程数
  151. if (!completedAbruptly) {
  152. int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
  153. if (min == 0 && ! workQueue.isEmpty())
  154. min = 1;
  155. // 如果满足最小线程要求,则直接返回
  156. if (workerCountOf(c) >= min)
  157. return; // replacement not needed
  158. }
  159. // 否则再添加一个Worker到线程池中备用
  160. // 非正常退出,会直接再添加一个Worker
  161. addWorker(null, false);
  162. }
  163. }
  164.  
  165. /**
  166. * Performs blocking or timed wait for a task, depending on
  167. * current configuration settings, or returns null if this worker
  168. * must exit because of any of:
  169. * 1. There are more than maximumPoolSize workers (due to
  170. * a call to setMaximumPoolSize).
  171. * 2. The pool is stopped.
  172. * 3. The pool is shutdown and the queue is empty.
  173. * 4. This worker timed out waiting for a task, and timed-out
  174. * workers are subject to termination (that is,
  175. * {@code allowCoreThreadTimeOut || workerCount > corePoolSize})
  176. * both before and after the timed wait, and if the queue is
  177. * non-empty, this worker is not the last thread in the pool.
  178. *
  179. * @return task, or null if the worker must exit, in which case
  180. * workerCount is decremented
  181. */
  182. private Runnable getTask() {
  183. boolean timedOut = false; // Did the last poll() time out?
  184.  
  185. for (;;) {
  186. int c = ctl.get();
  187. int rs = runStateOf(c);
  188.  
  189. // Check if queue empty only if necessary.
  190. // 如果进行了 shutdown, 且队列为空, 则需要将 worker 退出
  191. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
  192. // do {} while (! compareAndDecrementWorkerCount(ctl.get()));
  193. decrementWorkerCount();
  194. return null;
  195. }
  196.  
  197. int wc = workerCountOf(c);
  198.  
  199. // Are workers subject to culling?
  200. boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
  201. // 线程数据大于最大允许线程,需要删除多余的 Worker
  202. if ((wc > maximumPoolSize || (timed && timedOut))
  203. && (wc > 1 || workQueue.isEmpty())) {
  204. if (compareAndDecrementWorkerCount(c))
  205. return null;
  206. continue;
  207. }
  208.  
  209. try {
  210. // 如果开户了超时删除功能,则使用 poll, 否则使用 take() 进行阻塞获取
  211. Runnable r = timed ?
  212. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
  213. workQueue.take();
  214. // 获取到任务,则可以进行执行了
  215. if (r != null)
  216. return r;
  217. // 如果有超时设置,则会在下一循环时退出
  218. timedOut = true;
  219. }
  220. // 忽略中断异常
  221. // 在这种情况下,Worker如何响应外部的中断请求呢??? 思考
  222. catch (InterruptedException retry) {
  223. timedOut = false;
  224. }
  225. }
  226. }

  所以,Worker的作用就体现出来了,一个循环取任务执行任务过程:

    1. 有一个主循环一直进行任务的获取;
    2. 针对有超时的设置,会使用poll进行获取任务,如果超时,则 Worker 将会退出循环结束线程;
    3. 无超时的设置,则会使用 take 进行阻塞式获取,直到有值;
    4. 获取任务执行前置+业务+后置任务;
    5. 当获取到null的任务之后,当前Worker将会结束;
    6. 当前Worker结束后,将会判断是否有必要维护最低Worker数,从而决定是否再添加Worker进来。

  还是借用一个网上同学比较通用的一个图来表述下 Worker/ThreadPoolExecutor 的工作流程吧(已经很完美,不需要再造这轮子了)

5. shutdown 操作实现

  ThreadPoolExecutor 是通过 ctl 这个变量进行全局状态维护的,shutdown 在线程池中也是表现为一个状态,所以应该是比较简单的。

  1. /**
  2. * Initiates an orderly shutdown in which previously submitted
  3. * tasks are executed, but no new tasks will be accepted.
  4. * Invocation has no additional effect if already shut down.
  5. *
  6. * <p>This method does not wait for previously submitted tasks to
  7. * complete execution. Use {@link #awaitTermination awaitTermination}
  8. * to do that.
  9. *
  10. * @throws SecurityException {@inheritDoc}
  11. */
  12. public void shutdown() {
  13. // 为保证线程安全,使用 mainLock
  14. final ReentrantLock mainLock = this.mainLock;
  15. mainLock.lock();
  16. try {
  17. // SecurityManager 检查
  18. checkShutdownAccess();
  19. // 设置状态为 SHUTDOWN
  20. advanceRunState(SHUTDOWN);
  21. // 中断空闲的 Worker, 即相当于依次关闭每个空闲线程
  22. interruptIdleWorkers();
  23. // 关闭钩子,默认实现为空操作,为方便子类实现自定义清理功能
  24. onShutdown(); // hook for ScheduledThreadPoolExecutor
  25. } finally {
  26. mainLock.unlock();
  27. }
  28. // 再
  29. tryTerminate();
  30. }
  31. /**
  32. * Transitions runState to given target, or leaves it alone if
  33. * already at least the given target.
  34. *
  35. * @param targetState the desired state, either SHUTDOWN or STOP
  36. * (but not TIDYING or TERMINATED -- use tryTerminate for that)
  37. */
  38. private void advanceRunState(int targetState) {
  39. for (;;) {
  40. int c = ctl.get();
  41. // 自身CAS更新成功或者被其他线程更新成功
  42. if (runStateAtLeast(c, targetState) ||
  43. ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
  44. break;
  45. }
  46. }
  47. // 关闭空闲线程(非 running 状态)
  48. /**
  49. * Common form of interruptIdleWorkers, to avoid having to
  50. * remember what the boolean argument means.
  51. */
  52. private void interruptIdleWorkers() {
  53. // 上文已介绍, 此处 ONLY_ONE 为 false, 即是最大可能地中断所有 Worker
  54. interruptIdleWorkers(false);
  55. }
  56.  
  57. shutdown 对应的,有一个 shutdownNow, 其语义是 立即停止所有任务。
  58. /**
  59. * Attempts to stop all actively executing tasks, halts the
  60. * processing of waiting tasks, and returns a list of the tasks
  61. * that were awaiting execution. These tasks are drained (removed)
  62. * from the task queue upon return from this method.
  63. *
  64. * <p>This method does not wait for actively executing tasks to
  65. * terminate. Use {@link #awaitTermination awaitTermination} to
  66. * do that.
  67. *
  68. * <p>There are no guarantees beyond best-effort attempts to stop
  69. * processing actively executing tasks. This implementation
  70. * cancels tasks via {@link Thread#interrupt}, so any task that
  71. * fails to respond to interrupts may never terminate.
  72. *
  73. * @throws SecurityException {@inheritDoc}
  74. */
  75. public List<Runnable> shutdownNow() {
  76. List<Runnable> tasks;
  77. final ReentrantLock mainLock = this.mainLock;
  78. mainLock.lock();
  79. try {
  80. checkShutdownAccess();
  81. // 与 shutdown 的差别,设置的状态不一样
  82. advanceRunState(STOP);
  83. // 强行中断线程
  84. interruptWorkers();
  85. // 将未完成的任务返回
  86. tasks = drainQueue();
  87. } finally {
  88. mainLock.unlock();
  89. }
  90. tryTerminate();
  91. return tasks;
  92. }
  93.  
  94. /**
  95. * Interrupts all threads, even if active. Ignores SecurityExceptions
  96. * (in which case some threads may remain uninterrupted).
  97. */
  98. private void interruptWorkers() {
  99. final ReentrantLock mainLock = this.mainLock;
  100. mainLock.lock();
  101. try {
  102. for (Worker w : workers)
  103. // 调用 worker 的提供的中断方法
  104. w.interruptIfStarted();
  105. } finally {
  106. mainLock.unlock();
  107. }
  108. }
  109. // ThreadPoolExecutor.Worker#interruptIfStarted
  110. void interruptIfStarted() {
  111. Thread t;
  112. if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
  113. try {
  114. // 直接调用任务的 interrupt
  115. t.interrupt();
  116. } catch (SecurityException ignore) {
  117. }
  118. }
  119. }

6. invokeAll 的实现方式

  invokeAll, 望文生义,即是调用所有给定的任务。想来应该是一个个地添加任务到线程池队列吧。

  1. // invokeAll 的方法直接在抽象方便中就实现了,它的语义是同时执行n个任务,并同步等待结果返回
  2. // java.util.concurrent.AbstractExecutorService#invokeAll(java.util.Collection<? extends java.util.concurrent.Callable<T>>)
  3. public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
  4. throws InterruptedException {
  5. if (tasks == null)
  6. throw new NullPointerException();
  7. ArrayList<Future<T>> futures = new ArrayList<Future<T>>(tasks.size());
  8. boolean done = false;
  9. try {
  10. for (Callable<T> t : tasks) {
  11. RunnableFuture<T> f = newTaskFor(t);
  12. futures.add(f);
  13. // 依次调用各子类的实现,添加任务
  14. execute(f);
  15. }
  16. for (int i = 0, size = futures.size(); i < size; i++) {
  17. Future<T> f = futures.get(i);
  18. if (!f.isDone()) {
  19. try {
  20. // 依次等待执行结果
  21. f.get();
  22. } catch (CancellationException ignore) {
  23. } catch (ExecutionException ignore) {
  24. }
  25. }
  26. }
  27. done = true;
  28. return futures;
  29. } finally {
  30. if (!done)
  31. for (int i = 0, size = futures.size(); i < size; i++)
  32. futures.get(i).cancel(true);
  33. }
  34. }

  实现很简单,都是些外围调用。

7. ThreadPoolExecutor 的状态值的设计

  通过上面的过程,可以看到,整个ThreadPoolExecutor 非状态的依赖是非常强的。所以一个好的状态值的设计就显得很重要了,runState 代表线程池或者 Worker 的运行状态。如下:

  1. // runState is stored in the high-order bits
  2. // 整个状态使值使用 ctl 的高三位值进行控制, COUNT_BITS=29
  3. // 1110 0000 0000 0000
  4. private static final int RUNNING = -1 << COUNT_BITS;
  5. // 0000 0000 0000 0000
  6. private static final int SHUTDOWN = 0 << COUNT_BITS;
  7. // 0010 0000 0000 0000
  8. private static final int STOP = 1 << COUNT_BITS;
  9. // 0100 0000 0000 0000
  10. private static final int TIDYING = 2 << COUNT_BITS;
  11. // 0110 0000 0000 0000
  12. private static final int TERMINATED = 3 << COUNT_BITS;
  13. // 整个状态值的大小顺序主: RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
  14.  
  15. // 而低 29位,则用来保存 worker 的数量,当worker增加时,只要将整个 ctl 增加即可。
  16. // 0001 1111 1111 1111, 即是最大的 worker 数量
  17. private static final int CAPACITY = (1 << COUNT_BITS) - 1;
  18.  
  19. // 整个 ctl 描述为一个 AtomicInteger, 功能如下:
  20. /**
  21. * The main pool control state, ctl, is an atomic integer packing
  22. * two conceptual fields
  23. * workerCount, indicating the effective number of threads
  24. * runState, indicating whether running, shutting down etc
  25. *
  26. * In order to pack them into one int, we limit workerCount to
  27. * (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2
  28. * billion) otherwise representable. If this is ever an issue in
  29. * the future, the variable can be changed to be an AtomicLong,
  30. * and the shift/mask constants below adjusted. But until the need
  31. * arises, this code is a bit faster and simpler using an int.
  32. *
  33. * The workerCount is the number of workers that have been
  34. * permitted to start and not permitted to stop. The value may be
  35. * transiently different from the actual number of live threads,
  36. * for example when a ThreadFactory fails to create a thread when
  37. * asked, and when exiting threads are still performing
  38. * bookkeeping before terminating. The user-visible pool size is
  39. * reported as the current size of the workers set.
  40. *
  41. * The runState provides the main lifecycle control, taking on values:
  42. *
  43. * RUNNING: Accept new tasks and process queued tasks
  44. * SHUTDOWN: Don't accept new tasks, but process queued tasks
  45. * STOP: Don't accept new tasks, don't process queued tasks,
  46. * and interrupt in-progress tasks
  47. * TIDYING: All tasks have terminated, workerCount is zero,
  48. * the thread transitioning to state TIDYING
  49. * will run the terminated() hook method
  50. * TERMINATED: terminated() has completed
  51. *
  52. * The numerical order among these values matters, to allow
  53. * ordered comparisons. The runState monotonically increases over
  54. * time, but need not hit each state. The transitions are:
  55. *
  56. * RUNNING -> SHUTDOWN
  57. * On invocation of shutdown(), perhaps implicitly in finalize()
  58. * (RUNNING or SHUTDOWN) -> STOP
  59. * On invocation of shutdownNow()
  60. * SHUTDOWN -> TIDYING
  61. * When both queue and pool are empty
  62. * STOP -> TIDYING
  63. * When pool is empty
  64. * TIDYING -> TERMINATED
  65. * When the terminated() hook method has completed
  66. *
  67. * Threads waiting in awaitTermination() will return when the
  68. * state reaches TERMINATED.
  69. *
  70. * Detecting the transition from SHUTDOWN to TIDYING is less
  71. * straightforward than you'd like because the queue may become
  72. * empty after non-empty and vice versa during SHUTDOWN state, but
  73. * we can only terminate if, after seeing that it is empty, we see
  74. * that workerCount is 0 (which sometimes entails a recheck -- see
  75. * below).
  76. */
  77. private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

8. awaitTermination 等待关闭完成

  从上面的 shutdown, 可以看到,只是写了 SHUTDOWN 标识后,尝试尽可能地中断停止Worker线程,但并不保证中断成功。要想保证停止完成,需要有另外的机制来保证。从 awaitTermination 的语义来说,它是能保证任务停止完成的,那么它是如何保证的呢?

  1. // ThreadPoolExecutor.awaitTermination()
  2. public boolean awaitTermination(long timeout, TimeUnit unit)
  3. throws InterruptedException {
  4. long nanos = unit.toNanos(timeout);
  5. final ReentrantLock mainLock = this.mainLock;
  6. mainLock.lock();
  7. try {
  8. for (;;) {
  9. // 只是循环 ctl 状态, 只要 状态为 TERMINATED 状态,则说明已经关闭成功
  10. // 此处 termination 的状态触发是在 tryTerminate 中触发的
  11. if (runStateAtLeast(ctl.get(), TERMINATED))
  12. return true;
  13. if (nanos <= 0)
  14. return false;
  15. nanos = termination.awaitNanos(nanos);
  16. }
  17. } finally {
  18. mainLock.unlock();
  19. }
  20. }

  看起来, awaitTermination 并没有什么特殊操作,而是一直在等待。所以 TERMINATED 是 Worker 自行发生的动作。

  那是在哪里做的操作呢?其实是在获取任务的时候,会检测当前状态是否是 SHUTDOWN, 如果是SHUTDOWN且 队列为空,则会触发获取任务的返回null.从而结束当前 Worker.

  Worker 在结束前会调用 processWorkerExit() 方法,里面会再次调用 tryTerminate(), 当所有 Worker 都运行到这个点后, awaitTermination() 就会收到通知了。(注意: processWorkerExit() 会在每次运行后进行 addWorker() 尝试,但是在 SHUTDOWN 状态的添加操作总是失败的,所以不用考虑)

  到此,你是否可以解答前面的几个问题了呢?

  

线程池技术之:ThreadPoolExecutor 源码解析的更多相关文章

  1. Java 多线程(五)—— 线程池基础 之 FutureTask源码解析

    FutureTask是一个支持取消行为的异步任务执行器.该类实现了Future接口的方法. 如: 取消任务执行 查询任务是否执行完成 获取任务执行结果(”get“任务必须得执行完成才能获取结果,否则会 ...

  2. 并发编程(十二)—— Java 线程池 实现原理与源码深度解析 之 submit 方法 (二)

    在上一篇<并发编程(十一)—— Java 线程池 实现原理与源码深度解析(一)>中提到了线程池ThreadPoolExecutor的原理以及它的execute方法.这篇文章是接着上一篇文章 ...

  3. Java并发指南12:深度解读 java 线程池设计思想及源码实现

    ​深度解读 java 线程池设计思想及源码实现 转自 https://javadoop.com/2017/09/05/java-thread-pool/hmsr=toutiao.io&utm_ ...

  4. ThreadPoolExecutor系列<三、ThreadPoolExecutor 源码解析>

    本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.html 在源码解析前,需要先理清线程池控制的运行状态 ...

  5. ThreadPoolExecutor系列三——ThreadPoolExecutor 源码解析

    ThreadPoolExecutor 源码解析 本文系作者原创,转载请注明出处:http://www.cnblogs.com/further-further-further/p/7681826.htm ...

  6. 【转载】深度解读 java 线程池设计思想及源码实现

    总览 开篇来一些废话.下图是 java 线程池几个相关类的继承结构: 先简单说说这个继承结构,Executor 位于最顶层,也是最简单的,就一个 execute(Runnable runnable) ...

  7. Java并发包源码学习系列:线程池ThreadPoolExecutor源码解析

    目录 ThreadPoolExecutor概述 线程池解决的优点 线程池处理流程 创建线程池 重要常量及字段 线程池的五种状态及转换 ThreadPoolExecutor构造参数及参数意义 Work类 ...

  8. 【Java并发编程】21、线程池ThreadPoolExecutor源码解析

    一.前言 JUC这部分还有线程池这一块没有分析,需要抓紧时间分析,下面开始ThreadPoolExecutor,其是线程池的基础,分析完了这个类会简化之后的分析,线程池可以解决两个不同问题:由于减少了 ...

  9. 第十三章 ThreadPoolExecutor源码解析

    ThreadPoolExecutor使用方式.工作机理以及参数的详细介绍,请参照<第十二章 ThreadPoolExecutor使用与工作机理 > 1.源代码主要掌握两个部分 线程池的创建 ...

  10. Java 1.7 ThreadPoolExecutor源码解析

    Java中使用线程池技术一般都是使用Executors这个工厂类,它提供了非常简单方法来创建各种类型的线程池: public static ExecutorService newFixedThread ...

随机推荐

  1. 洛谷$P$2468 粟粟的书架 $[SDOI2010]$ 主席树

    正解:主席树 解题报告: 传送门! 题目大意是说,给定一个矩形,然后每次会给一个,这个大矩形中的一个小矩形,询问从小矩形中最少选多少个数字能满足它们之和大于等于给定数字$x$ 看起来很神的样子,完全不 ...

  2. centos7.3安装chrome

    Centos7安装chrome浏览器 1.配置yum源 在目录 /etc/yum.repos.d/ 下新建文件 google-chrome.repo cd /ect/yum.repos.d/ vim ...

  3. [开源] SEPP——研发协作一站式管理平台

    演示地址 http://www.seqcer.com/ 仅对chrome浏览器做了完全适配,其他chromium核心浏览器或者firefox.safari也能使用,但是不推荐 仓库地址: 前端:htt ...

  4. cassandra中的ACID,与RDBMS中的事务有何不同?

    Cassandra中的ACID标准 Apache Cassandra不遵循具有回滚或锁定机制的ACID(原子性,一致性,隔离性,持久性)事务,而是提供原子,隔离和持久的事务,并具有最终和可调的一致性, ...

  5. schedule of 2016-09-12~2016-09-18(Monday~Sunday)——1st semester of 2nd Grade

    2016/9/12 Monday 1.send present to Teacher Wei&hu 2.make ppt for 1st database 2.0 meeting for al ...

  6. Docker——WIN7 安装 Docker实战与入门

    1.Docker简介 Docker 是一个开源项目,诞生于 2013 年初,最初是 dotCloud 公司内部的一个业余项目.它基于 Google 公司推出的 Go 语言实现. 项目后来加入了 Lin ...

  7. docker 批量删除 镜像 容器

    我们在docker构建和测试时,经常会产生很多无用的镜像或者容器,我们可用如下两条命令一个一个删除. docker container rm 容器id #删除容器 可简写: docker rm 容器i ...

  8. Office系列(1)---将Office文件(Word、PPT、Excel)转换为PDF文件

    需求: 将Office文件作为文章并在网页上预览,主要为(Word.PPT.Excel)3种类型文件. 研究了一下,找到了两种解决方案 直接调用微软的在线预览功能实现(预览前提:预览资源必须可以直接通 ...

  9. org.springframework.core.type.classreading.ClassMetadataReadingVisitor 异常

    今天项目启动的时候发现了一个异常: Exception in thread "main" org.springframework.beans.factory.BeanDefinit ...

  10. Django设置 DEBUG=False后静态文件无法加载解决

    前段时间调试一直是在Debug=True先运行的,没有什么问题.今天关闭了Debug后,出现了一个问题.就是静态文件找不到了,「img.css.js」都提示404,无法准确的访问 static 静态文 ...