java并发编程之源码分析ThreadPoolExecutor线程池实现原理

  • Rejected tasks
  • 任务拒绝策略

1)AbortPolicy,抛出运行时异常

2)CallerRunsPolicy 调用者直接运行,不在线程中运行。

3)DiscardPolicy 直接将任务丢弃

4)DiscardOldestPolicy 丢弃队列中头部的任务。

  • New tasks submitted in method {@link #execute} will be
  • rejected when the Executor has been shut down, and also

  • when the Executor uses finite bounds for both maximum threads and

  • work queue capacity, and is saturated. In either case, the {@code

  • execute} method invokes the {@link

  • RejectedExecutionHandler#rejectedExecution} method of its {@link

  • RejectedExecutionHandler}. Four predefined handler policies are

  • provided:

    1. In the default {@link ThreadPoolExecutor.AbortPolicy}, the
    2. handler throws a runtime {@link RejectedExecutionException} upon

    3. rejection.

    4. In {@link ThreadPoolExecutor.CallerRunsPolicy}, the thread
    5. that invokes {@code execute} itself runs the task. This provides a

    6. simple feedback control mechanism that will slow down the rate that

    7. new tasks are submitted.

    8. In {@link ThreadPoolExecutor.DiscardPolicy}, a task that
    9. cannot be executed is simply dropped.

    10. In {@link ThreadPoolExecutor.DiscardOldestPolicy}, if the
    11. executor is not shut down, the task at the head of the work queue

    12. is dropped, and then execution is retried (which can fail again,

    13. causing this to be repeated.)

    14. It is possible to define and use other kinds of {@link

    15. RejectedExecutionHandler} classes. Doing so requires some care

    16. especially when policies are designed to work only under particular

    17. capacity or queuing policies.

    18. Hook methods
    19. This class provides {@code protected} overridable {@link
    20. #beforeExecute} and {@link #afterExecute} methods that are called

    21. before and after execution of each task. These can be used to

    22. manipulate the execution environment; for example, reinitializing

    23. ThreadLocals, gathering statistics, or adding log

    24. entries. Additionally, method {@link #terminated} can be overridden

    25. to perform any special processing that needs to be done once the

    26. Executor has fully terminated.

    27. If hook or callback methods throw exceptions, internal worker

    28. threads may in turn fail and abruptly terminate.

    29. Queue maintenance
    30. Method {@link #getQueue} allows access to the work queue for
    31. purposes of monitoring and debugging. Use of this method for any

    32. other purpose is strongly discouraged. Two supplied methods,

    33. {@link #remove} and {@link #purge} are available to assist in

    34. storage reclamation when large numbers of queued tasks become

    35. cancelled.

    36. Finalization
    37. A pool that is no longer referenced in a program AND
    38. has no remaining threads will be {@code shutdown} automatically. If

    39. you would like to ensure that unreferenced pools are reclaimed even

    40. if users forget to call {@link #shutdown}, then you must arrange

    41. that unused threads eventually die, by setting appropriate

    42. keep-alive times, using a lower bound of zero core threads and/or

    43. setting {@link #allowCoreThreadTimeOut(boolean)}.

    44. Extension example. Most extensions of this class

    45. override one or more of the protected hook methods. For example,

    46. here is a subclass that adds a simple pause/resume feature:

    47.  {@code
      
    48. class PausableThreadPoolExecutor extends ThreadPoolExecutor {

    49. private boolean isPaused;

    50. private ReentrantLock pauseLock = new ReentrantLock();

    51. private Condition unpaused = pauseLock.newCondition();

    52. public PausableThreadPoolExecutor(…) { super(…); }

    53. protected void beforeExecute(Thread t, Runnable r) {

    54. super.beforeExecute(t, r);
      
    55. pauseLock.lock();
      
    56. try {
      
    57.   while (isPaused) unpaused.await();
      
    58. } catch (InterruptedException ie) {
      
    59.   t.interrupt();
      
    60. } finally {
      
    61.   pauseLock.unlock();
      
    62. }
      
    63. }

    64. public void pause() {

    65. pauseLock.lock();
      
    66. try {
      
    67.   isPaused = true;
      
    68. } finally {
      
    69.   pauseLock.unlock();
      
    70. }
      
    71. }

    72. public void resume() {

    73. pauseLock.lock();
      
    74. try {
      
    75.   isPaused = false;
      
    76.   unpaused.signalAll();
      
    77. } finally {
      
    78.   pauseLock.unlock();
      
    79. }
      
    80. }

    81. }}

    82. @since 1.5

    83. @author Doug Lea

    */

    2、ThreadPoolExecutors 内部数据结构与构造方法详解

    ===================================

    ThreadPoolExecutors的完整构造函数如下,从构造函数中能得出线程池最核心的属性。

    /**

    • Creates a new {@code ThreadPoolExecutor} with the given initial

    • parameters.

    • @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.
      
    • @param threadFactory the factory to use when the executor

    •    creates a new thread
      
    • @param handler the handler to use when execution is blocked

    •    because the thread bounds and queue capacities are reached
      
    • @throws IllegalArgumentException if one of the following holds:

    •     {@code corePoolSize < 0}<br>
      
    •     {@code keepAliveTime < 0}<br>
      
    •     {@code maximumPoolSize <= 0}<br>
      
    •     {@code maximumPoolSize < corePoolSize}
      
    • @throws NullPointerException if {@code workQueue}

    •     or {@code threadFactory} or {@code handler} is null
      

    */

    public ThreadPoolExecutor(int corePoolSize,

    int maximumPoolSize,

    long keepAliveTime,

    TimeUnit unit,

    BlockingQueue workQueue,

    ThreadFactory threadFactory,

    RejectedExecutionHandler handler) {

    if (corePoolSize < 0 ||

    maximumPoolSize <= 0 ||

    maximumPoolSize < corePoolSize ||

    keepAliveTime < 0)

    throw new IllegalArgumentException();

    if (workQueue == null || threadFactory == null || handler == null)

    throw new NullPointerException();

    this.corePoolSize = corePoolSize;

    this.maximumPoolSize = maximumPoolSize;

    this.workQueue = workQueue;

    this.keepAliveTime = unit.toNanos(keepAliveTime);

    this.threadFactory = threadFactory;

    this.handler = handler;

    }

    • corePoolSize 核心线程数

    • maximumPoolSize 最大线程数

    • keepAliveTime 线程保持激活状态的时间,如果为0,永远处于激活状态

    • unit ,keepAliveTime的单位

    • workQueue,线程池使用的队列

    • threadFactory 创建线程的工厂

    • handler 当队列已满,无更大线程处理任务时的拒绝任务的策略。

    除了这些核心参数外,我觉得有必要再关注如下。

    • HashSet workers

    • completedTaskCount 完成的任务数

    • allowCoreThreadTimeOut,该值默认为false,也就是默认keepAliveTime不会生效。

    3、核心源码分析

    ========

    3.1 线程状态与几个基础方法设计原理


    /**

    • The main pool control state, ctl, is an atomic integer packing

    • two conceptual fields

    • workerCount, indicating the effective number of threads

    • runState, indicating whether running, shutting down etc

    • In order to pack them into one int, we limit workerCount to

    • (2^29)-1 (about 500 million) threads rather than (2^31)-1 (2

    • billion) otherwise representable. If this is ever an issue in

    • the future, the variable can be changed to be an AtomicLong,

    • and the shift/mask constants below adjusted. But until the need

    • arises, this code is a bit faster and simpler using an int.

    • The workerCount is the number of workers that have been

    • permitted to start and not permitted to stop. The value may be

    • transiently different from the actual number of live threads,

    • for example when a ThreadFactory fails to create a thread when

    • asked, and when exiting threads are still performing

    • bookkeeping before terminating. The user-visible pool size is

    • reported as the current size of the workers set.

    • The runState provides the main lifecyle control, taking on values:

    • 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

    • The numerical order among these values matters, to allow

    • ordered comparisons. The runState monotonically increases over

    • time, but need not hit each state. The transitions are:

    • RUNNING -> SHUTDOWN

    • On invocation of shutdown(), perhaps implicitly in finalize()

    • (RUNNING or SHUTDOWN) -> STOP

    • On invocation of shutdownNow()

    • SHUTDOWN -> TIDYING

    • When both queue and pool are empty

    • STOP -> TIDYING

    • When pool is empty

    • TIDYING -> TERMINATED

    • When the terminated() hook method has completed

    • Threads waiting in awaitTermination() will return when the

    • state reaches TERMINATED.

    • Detecting the transition from SHUTDOWN to TIDYING is less

    • straightforward than you’d like because the queue may become

    • empty after non-empty and vice versa during SHUTDOWN state, but

    • we can only terminate if, after seeing that it is empty, we see

    • that workerCount is 0 (which sometimes entails a recheck – see

    • below).

    */

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static final int COUNT_BITS = Integer.SIZE - 3;

    private static final int CAPACITY = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits

    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;

    // Packing and unpacking ctl

    private static int runStateOf(int c) { return c & ~CAPACITY; }

    private static int workerCountOf(int c) { return c & CAPACITY; }

    private static int ctlOf(int rs, int wc) { return rs | wc; }

    private static boolean isRunning(int c) {

    return c < SHUTDOWN;

    }

    相关源码解读:

    不知大家有没有想过为什么线程池的状态简单的定义为 -1,0,1,2,3 不就得了,为什么还要用移位操作呢?

    原来这样的,ThreadPool ctl 的这个变量的设计哲学是用int的高3位 + 29个0代表状态,用高位000+低29位来表示线程池中工作线程的数量,太佩服了。

    首先 CAPACITY 的值为 workCount 的最大容量,该值为 000 11111 11111111 11111111 11111111,29个1,

    我们来看一下

    private static int runStateOf(int c)     { return c & ~CAPACITY; }

    用ctl里面的值与容量取反的方式获取状态值。由于CAPACITY的值为000 11111 11111111 11111111 11111111,那取反后为111 00000 00000000 00000000 00000000, 用 c 与 该值进行与运算,这样就直接保留了c的高三位,然后将c的低29位设置为0,这不就是线程池状态的存放规则吗,绝。

    根据此方法,不难得出计算workCount的方法。

    private static int ctlOf(int rs, int wc) { return rs | wc; }

    该方法,主要是用来更新运行状态的。确保工作线程数量不丢失。

    线程池状态以及含义。

    • RUNNING        运行态

    • SHUTDOWN    关闭,此时不接受新的任务,但继续处理队列中的任务。

    • STOP                停止,此时不接受新的任务,不处理队列中的任务,并中断正在执行的任务

    • TIDYING          所有的工作线程全部停止,并工作线程数量为0,将调用terminated方法,进入到TERMINATED

    • TERMINATED  终止状态

    线程池默认状态 RUNNING

    如果调用shutdwon() 方法,状态从 RUNNING —>  SHUTDOWN。

    如果调用shutdwonNow()方法,状态从RUUNING|SHUTDOWN—>STOP。

    SHUTDOWN —> TIDYING 。

    队列为空并且线程池空。

    STOP --> TIDYING。

    线程池为空。

    线程池设计原理:

    • 线程池的工作线程为ThreadPoolExecutors的Worker线程,无论是submit还是executor方法中传入的Callable task,Runable参数,只是实现了Runnable接口,在线程池的调用过程,不会调用其start方法,只会调用Worker线程的start方法,然后在Worker线程的run方法中会调用入参的run方法。

    • 众所周知,线程的生命周期在run方法运行结束后(包括异常退出)就结束。要想重复利用线程,我们要确保工作线程Worker的run方法运行在一个无限循环中,然后从任务队列中一个一个获取对象,如果任务队列为空,则阻塞,当然需要提供一些控制,结束无限循环,来销毁线程。在源码 runWorker方法与getTask来实现。

    大概的实现思路是 如果 getTask 返回null, 则该 worker 线程将被销毁。

    那 getTask 在什么情况下会返回 false 呢?

    1. 如果线程池的状态为SHUTDOWN并且队列不为空

    2. 如果线程池的状态大于STOP

    3. 如果当前运行的线程数大于核心线程数,会返回null,已销毁该worker线程

    对keepAliveTime的理解,如果allowCoreThreadTimeOut为真,那么keepAliveTime其实就是从任务队列获取任务等待的超时时间,也就是workerQueue.poll(keepALiveTime, TimeUnit.NANOSECONDS)

    3.2 FUture submit(Callable task) 方法详解


    在看的代码的过程中,只要明白了上述基础方法,后面的代码看起来清晰可见,故,我只列出关键方法,大家可以浏览,应该不难。

    /**

    • Submits a value-returning task for execution and returns a

    • Future representing the pending results of the task. The

    • Future’s get method will return the task’s result upon

    • successful completion.

    • If you would like to immediately block waiting

    • for a task, you can use constructions of the form

    • result = exec.submit(aCallable).get();

    • Note: The {@link Executors} class includes a set of methods

    • that can convert some other common closure-like objects,

    • for example, {@link java.security.PrivilegedAction} to

    • {@link Callable} form so they can be submitted.

    • @param task the task to submit

    • @return a Future representing pending completion of the task

    • @throws RejectedExecutionException if the task cannot be

    •     scheduled for execution
      
    • @throws NullPointerException if the task is null

    */

    Future submit(Callable task);

    提交一个任务,并返回结构到Future,Future就是典型的Future设计模式,就是提交任务到线程池后,返回一个凭证,并直接返回,主线程继续执行,然后当线程池将任务运行完毕后,再将结果填充到凭证中,当主线程调用凭证future的get方法时,如果结果还未填充,则阻塞等待。

    现将Callable与Future接口的源代码贴出来,然后重点分析submit方法的实现。

    public interface Callable {

    /**

    • Computes a result, or throws an exception if unable to do so.

    • @return computed result

    • @throws Exception if unable to compute a result

    */

    V call() throws Exception;

    }

    public interface Future {

    /**

    • Attempts to cancel execution of this task. This attempt will

    • fail if the task has already completed, has already been cancelled,

    • or could not be cancelled for some other reason. If successful,

    • and this task has not started when cancel is called,

    • this task should never run. If the task has already started,

    • then the mayInterruptIfRunning parameter determines

    • whether the thread executing this task should be interrupted in

    • an attempt to stop the task.

    • After this method returns, subsequent calls to {@link #isDone} will

    • always return true. Subsequent calls to {@link #isCancelled}

    • will always return true if this method returned true.

    • @param mayInterruptIfRunning true if the thread executing this

    • task should be interrupted; otherwise, in-progress tasks are allowed

    • to complete

    • @return false if the task could not be cancelled,

    • typically because it has already completed normally;

    • true otherwise

    */

    boolean cancel(boolean mayInterruptIfRunning);

    /**

    • Returns true if this task was cancelled before it completed

    • normally.

    • @return true if this task was cancelled before it completed

    */

    boolean isCancelled();

    /**

    • Returns true if this task completed.

    • Completion may be due to normal termination, an exception, or

    • cancellation – in all of these cases, this method will return

    • true.

    • @return true if this task completed

    */

    boolean isDone();

    /**

    • Waits if necessary for the computation to complete, and then

    • retrieves its result.

    • @return the computed result

    • @throws CancellationException if the computation was cancelled

    • @throws ExecutionException if the computation threw an

    • exception

    • @throws InterruptedException if the current thread was interrupted

    • while waiting

    */

    V get() throws InterruptedException, ExecutionException;

    /**

    • Waits if necessary for at most the given time for the computation

    • to complete, and then retrieves its result, if available.

    • @param timeout the maximum time to wait

    • @param unit the time unit of the timeout argument

    • @return the computed result

    • @throws CancellationException if the computation was cancelled

    • @throws ExecutionException if the computation threw an

    • exception

    • @throws InterruptedException if the current thread was interrupted

    • while waiting

    • @throws TimeoutException if the wait timed out

    */

    V get(long timeout, TimeUnit unit)

    throws InterruptedException, ExecutionException, TimeoutException;

    }

    现在开始探究submit的实现原理,该代码出自AbstractExecutorService中

    public Future<?> submit(Runnable task) {

    if (task == null) throw new NullPointerException();

    RunnableFuture ftask = newTaskFor(task, null);

    execute(ftask);

    return ftask;

    }

    protected RunnableFuture newTaskFor(Callable callable) {

    return new FutureTask(callable);

    }

    核心实现在ThreadPoolExecutor的execute方法

    /**

    • Executes the given task sometime in the future. The task

    • may execute in a new thread or in an existing pooled thread.

    • If the task cannot be submitted for execution, either because this

    • executor has been shutdown or because its capacity has been reached,

    • the task is handled by the current {@code RejectedExecutionHandler}.

    • @param command the task to execute

    • @throws RejectedExecutionException at discretion of

    •     {@code RejectedExecutionHandler}, if the task
      
    •     cannot be accepted for execution
      
    • @throws NullPointerException if {@code command} is null

    */

    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.

      1. 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.

      1. 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© < corePoolSize) { // @1

    if (addWorker(command, true)) // @2

    return;

    c = ctl.get(); //@3

    }

    if (isRunning© && 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);

    }

    代码@1,如果当前线程池中的线程数量小于核心线程数的话,尝试新增一个新的线程。所以我们把目光投入到addWorker方法中。

    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 (;😉 { // @1

    int c = ctl.get();

    int rs = runStateOf©; // @2

    // Check if queue empty only if necessary.

    if (rs >= SHUTDOWN && //@3

    ! (rs == SHUTDOWN &&

    firstTask == null &&

    ! workQueue.isEmpty()))

    return false;

    for (;😉 { //@4

    int wc = workerCountOf©;

    if (wc >= CAPACITY ||

    wc >= (core ? corePoolSize : maximumPoolSize)) //@5

    return false;

    if (compareAndIncrementWorkerCount©)

    break retry; // @6

    c = ctl.get(); // Re-read ctl

    if (runStateOf© != rs)

    continue retry; //@7

    // else CAS failed due to workerCount change; retry inner loop

    }

    }

    boolean workerStarted = false;

    boolean workerAdded = false;

    Worker w = null;

    try {

    final ReentrantLock mainLock = this.mainLock;

    w = new Worker(firstTask);

    final Thread t = w.thread;

    if (t != null) {

    mainLock.lock(); // @8

    try {

    // Recheck while holding lock.

    // Back out on ThreadFactory failure or if

    // shut down before lock acquired.

    int c = ctl.get();

    int rs = runStateOf©;

    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(); // 运行线程 // @9

    workerStarted = true;

    } //@8 end

    }

    } finally {

    if (! workerStarted)

    addWorkerFailed(w); // 增加工作线程失败

    }

    return workerStarted;

    }

    代码@1,外层循环(自旋模式)

    代码@2,获取线程池的运行状态

    代码@3,这里的判断条件,为什么不直接写 if(rs >= SHUTDOWN) return false;而要加第二个条件,目前不明白,等在了解到参数firstTask在什么情况下为空。在这里,我们目前只要知道,只有线程池的状态为 RUNNING时,线程池才接收新的任务,去增加工作线程。

    代码@4,内层循环,主要的目的就是利用CAS增加一个线程数量。

    代码@5,判断当前线程池的数量,如果数量达到规定的数量,则直接返回false,添加工作线程失败。

    代码@6,如果修改线程数量成功,则跳出循环,开始创建工作线程。

    代码@7,如果修改线程数量不成功(CAS)有两种情况:1、线程数量变化,重试则好,2,如果线程的运行状态变化,则重新开始外层循环,重新判断addWork流程。

    代码@8,在锁mainLock的保护下,完成 workers (HashSet)的维护。

    接着分析一下代码@9,启动线程,执行关键的方法 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.

      1. Before running any task, the lock is acquired to prevent
    • other pool interrupts while the task is executing, and

    • clearInterruptsForTaskRun called to ensure that unless pool is

    • stopping, this thread does not have its interrupt set.

      1. 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.

      1. 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.

      1. 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

    最后

    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

    深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

    因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

    既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

    如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

    由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
    f 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.

      1. 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

    最后

    自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

    深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

    因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

    [外链图片转存中…(img-82UYKT6z-1714885107404)]

    [外链图片转存中…(img-Egu0ZQnQ-1714885107405)]

    [外链图片转存中…(img-r4d2OqEV-1714885107405)]

    既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

    如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

    由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

    • 24
      点赞
    • 11
      收藏
      觉得还不错? 一键收藏
    • 0
      评论
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

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

    抵扣说明:

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

    余额充值