JAVA JUC并发编程 Part I (线程、线程池)

线程的常用方法

  • start() 和 run()

run方法是同步方法,而start是异步方法

run方法的作用是存放任务代码,而start的方法是启动线程

执行run方法它不会产生新线程,而执行start方法会产生新线程

run方法可以被调用无限次,而start方法只能被执行一次,原因就在于线程不能被重复启动

  • setName() 和 getName()

设置线程或者得到线程的名字

Thread.currentThread() 获取当前线程

  • sleep()

线程睡眠,一般用以测试,

要去完成延迟任务,一般我们使用mq的延迟消息或者是定时任务

如果使用死循环完成线程的持久化会导致cpu被占用,严重影响性能,

tomcat中就是创建了一个阻塞的非守护线程来阻止立即关闭,源码就是用了sleep方法来进行持久化的,这样不会一直占用cpu,

Thread.sleep(time) 单位:毫秒

我们也可以使用工具类 TimeUnit 里的方法完成线程的休眠

  • notify all

唤醒(全部)线程

  • interrupt()

中断当前线程,但不是直接停止当前的线程,仅仅是设置线程的中断状态为true,告诉线程需要中断,我们需要通Thread.interrupted() 判断并执行相应操作停止当前线程操作。

if (Thread.interrupted()) { // 判断为true 然后置为 fasle
    break;
}

当线程在sleep中时打断线程会导致程序抛出异常 InterruptedException

Thread.interrupted() 清楚打断标记 --> 撤销打断

name.isInterrpted() 获取打断状态, 不清除打断标记 --> 不撤销打断

  • yield()

提示线程调度器尽力让出当前线程对cpu的占用,并不能保证一定让出,具体是否让出还得看线程调度器,

  • setPriority() 和 getPriority()

设置线程的优先级

也只是通知一下,当cpu资源够用时,也不会直接占满cpu,而是自动调度和分配

常用值:1、5、10

  • join()

等待这个(调用)线程结束

// 假如有子线程t1 和主线程main
// ...
t1.join(); // 可以设置等待时间 单位:毫秒
// main会等待t1执行完成...
  • isAlive()

判断线程是否存活 t1.isAlive()…

  • getState()

获取线程的状态 New、Runnable、Waiting、Timed Waiting、Blocked、Terminated

  • setDaemon() 守护线程

线程分为用户(普通)线程和守护线程

t1.setDaemon(true);

守护线程就是主线程不会等待这个线程,一旦主线程结束那么守护线程会立马结束

一些应用:

垃圾回收器线程属于守护线程

tomcat用来接收处理外部的请求的下城就是守护线程

tomcat执行shutdown 所有正在请求的子线程会立马停止

线程的六种状态之间的转化细节

  • 线程不能直接从New状态到Blocked状态,必须先经过Runnable状态

  • New 和 Terminated 状态只有一次,只有中间的Runnable、Blocked、Waiting、Timed Waiting可以相互转换

  • 生命周期不可逆,一旦进入 Runnable 状态就不能回到 New 状态,一旦被终止就不可能再有任何状态变化

image-20230919221317903

Callable接口

一般情况下,使用Runnable接口、Thread实现的线程我们都是无法返回结果的。但是如果对一些场合需要线程返回的结果。就要使用Callable、Future这几个类。

Callable只能在ExecutorService的线程池中跑,但有返回结果,也可以通过返回的Future对象查询执行状态。

Future本身也是一种设计模式,它是用来取得异步任务的结果,看看其源码:

package java.util.concurrent;

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}
package com.zky;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class TestClass {

    public static void main(String[] args) {
        // 实例化
        FutureTask<Integer> future = new FutureTask<Integer>(() -> {
            System.out.println("子线程运行中...");
            return 6;
        });
        // 启动线程
        new Thread(future).start();

        try {
            Integer value = future.get();
            System.out.println(value);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }
}

Q:创建线程的三种方式

  • 实现Runnable接口的run方法
  • 集成Thread类并重写run的方法
  • 使用FutureTask方式(实现Callable接口的方式)

线程池

Q:为什么需要线程池?

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?

在Java中可以通过线程池来达到这样的效果。
线程池: 其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。

Q:线程池主要起到了什么作用?

控制运行的线程数量,处理过程中将任务放入队列,然在线程创建后创建这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行

主要特点为:线程复用、控制最大并发数、管理线程

  • 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
  • 提高相应速度,当任务到达时,任务可以不需要等待线程创建就能立即执行
  • 提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控

Q:jdk中提供了那些线程池?

Executors提供的 4 种创建不同的线程池

  • newFixedThreadPool
  • newSingleThreadExecutor
  • newCachedThreadPool
  • newScheduledThreadPool
package com.zky;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class TestClass {

    public static void main(String[] args) {

        class Task implements Runnable {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }
        Task task = new Task();

        ExecutorService threadPool = Executors.newFixedThreadPool(5); // 1、固定大小线程池

//        ExecutorService threadPool = Executors.newSingleThreadExecutor(); // 2、单线程就是1个

//        ExecutorService threadPool = Executors.newCachedThreadPool(); // 3、动态创建线程的线程池

//        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);// 4、可以定时执行调度

        // 只会创建5个线程,其余等待空闲线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(task); // 1、2、3
//                threadPool.schedule(task, 3, TimeUnit.SECONDS); // 4、定时调度 任务到来时等待多久后进行调度
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 关闭线程池 ==> 问题: 线程池中的线程一直占用系统资源,会导致内存泄露、主线程一直不会退出
            // (1)不会立马停止正在执行的线程,会等待所有任务执行完之后才彻底关闭,全部任务执行完就是5线程执行完10个任务后才会停止
            threadPool.shutdown();
            // (2)这个只会等待正在执行的线程完毕  5线程结束就会停止
//            threadPool.shutdownNow();

            try {
                // 等待线程池关闭 参数是愿意等待多久 等待线程池中的所有线程执行完
                threadPool.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            // 判断线程池 是否真正的终止了,并且代表线程池中的线程全部执行完毕
            System.out.println(threadPool.isTerminated());
        }
    }
}

Q:excute和submit有什么区别?

package com.zky;

import java.util.concurrent.*;

public class TestClass {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        ExecutorService threadPool = Executors.newFixedThreadPool(5); 

        // 1. 参数
        // executor Runnable.run
        // submit callable
        
        // 2. 返回值
        // execute 的返回值为 void
        // submit 底层帮助我们创建了FutureTask对象 返回值就是futuretask

        // Q: 为什么execute也可以执行带返回值的线程
        FutureTask<Integer> future1 = new FutureTask<>(() -> {
            System.out.println("ing...");
            return 6;
        });
        threadPool.execute(future1);
        System.out.println(future1.get());

        // 这里submit底层帮助我们创建了FutureTask对象 等于就是少了自己创建FutureTask的一个步骤
        Future<Integer> future2 = threadPool.submit(() -> {
            System.out.println("ing...");
            return 5;
        });
        System.out.println(future2.get());

        // 3. 异常
        // execute 会在子线程中抛出异常 在主线程中捕捉不到
        // submit 不会立马抛出异常,而是将异常暂时存储起来 等我们调用 future.get()方法才会抛出异常, 可以在主线程中捕捉到,更方便处理异常
        try {
            Future<?> future = threadPool.submit(() -> {
                int a = 1 / 0;
            });
            future.get();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Q:线程池的参数都有哪些?

参数和原理(自定义线程池)

    public ThreadPoolExecutor(int corePoolSize,		// 核心线程数量
                              int maximumPoolSize,		// 最大线程数量
                              long keepAliveTime,		// 非核心线程 空闲状态的存活时间
                              TimeUnit unit,		// keepAliveTime 时间单位(天、小时...)
                              BlockingQueue<Runnable> workQueue,		// 阻塞队列(阻塞队列)
                              ThreadFactory threadFactory,		// 线程工厂,就是用来创建线程的
                              RejectedExecutionHandler handler		// 拒绝策略
                             ) {
        //...
    }

可以传入以上的对应的参数根据需求来创建自己的线程池

Q:线程池创建和执行流程是什么?

image-20230920181550505

创建优先:核心 ——> 阻塞 ——> 最大
执行优先:核心 ——> 最大 、 阻塞

Q:tomcat的线程池和jdk的线程池的区别?

tomcat中的线程池和jdk中的线程池差不多,他就是在new ThreadPoolExecutor(…) 最后会调用一个 prestartAllCoreThreads() 方法,

顾名思义,提前启动所有核心线程池。省去了创建线程的时间。

在JDK的线程池中,prestartAllCoreThreads方法用于预先创建核心线程,数量等于核心线程池的大小。这样做的目的是为了在需要执行任务时,可以立即有可用的线程来处理,提高程序的响应速度和效率。

此方法在初始化线程池后调用,会立即创建等于corePoolSize数量的核心线程。如果线程池中的当前线程数小于corePoolSize,则会创建新的线程;如果当前线程数已经等于corePoolSize,那么不会创建新的线程。

需要注意的是,如果线程池中的线程数量已经达到了corePoolSize,并且任务队列已满,那么新提交的任务可能会被拒绝。此时,prestartAllCoreThreads方法也不会创建新的线程。

Tomcat线程池和JDK线程池的主要区别在于它们处理任务的方式和效率。

首先来看JDK线程池。JDK线程池中,当核心线程池已满时,会将任务添加到任务队列中,等待线程池中的线程有空闲时进行处理。这种设计对于计算密集型任务来说是合理的,因为当线程数达到核心线程数时,意味着CPU可能已经相当繁忙,新任务需要等待线程空闲才能处理。

相比之下,Tomcat线程池的设计更偏向IO密集型。在Tomcat线程池中,当核心线程池已满时,会按照最大线程池的定义开始创建新的线程,直到达到最大线程数。这是因为对于IO密集型任务来说,CPU可能在大部分时间里都是空闲的,因此通过创建更多的线程来处理任务可以更有效地利用CPU资源。但是,当任务数量继续增加,超过最大线程数后,多余的任务会被添加到任务队列中,等待线程池中的线程有空闲时进行处理。

综上,Tomcat线程池和JDK线程池的主要区别在于它们处理任务的方式和效率。JDK线程池更适合计算密集型任务,而Tomcat线程池则更适合IO密集型任务。

Q:线程池如何创建线程?

以源码来看:

  • 如果运行的线程少于corePoolSize,请尝试以给定的命令作为第一个命令启动一个新线程任务。对addWorker的调用原子地检查runState和workerCount,这样可以防止错误警报线程,而不应该返回false。

  • 如果任务可以成功排队,那么我们仍然需要仔细检查我们是否应该添加一个线程(因为自上次检查以来已有的死亡)或池在进入此方法后关闭。所以我们重新检查状态,并在必要时回滚排队已停止,或者启动一个新线程(如果没有)。

  • 如果我们不能对任务进行排队,那么我们尝试添加一个新的螺纹。如果它失败了,我们就知道我们已经关闭或饱和了因此拒绝该任务。

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        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);
        }
        // 阻塞队列满了,又会创建新的线程,直到达到最大线程数, 后面的false表示非核心线程,
        // 这里可以看到线程池是不公平的,因为有的任务还在排队,现在新来的任务先执行了
        else if (!addWorker(command, false)) 
            reject(command); // 拒绝
    }

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; // 如果达到最大线程那么就直接return 就是拒绝策略的一种
            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;
}

Q:线程池中的拒绝策略(4种)?

  • ThreadPoolExecutor.AbortPolicy: 丢弃任务并抛出 RejectedExecutionException 异常
  • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

源码分析和作用

AbortPolicy

默认拒绝策略,简单暴力,直接抛异常

public static class AbortPolicy implements RejectedExecutionHandler {
    /**
     * Creates an {@code AbortPolicy}.
     */
    public AbortPolicy() { }

    /**
     * Always throws RejectedExecutionException.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     * @throws RejectedExecutionException always
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}
CallerRunsPolicy

会判断线程池是否关闭,会由调用者执行run方法

比如我们在主线程中调用execute方法,由于线程池中的子线程满了,所以会由调用者主线程去执行

@Test
void contextLoads() {
    ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 200, 0L, TimeUnit.SECONDS, new LinkedBlockingQueue<>());

    executor.execute(new Runnable() {
        @Override
        public void run() {        // 这里就是会由调用者 在这里也就是我们的主线程来执行
            System.out.println("ing...");
        }
    });
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code CallerRunsPolicy}.
     */
    public CallerRunsPolicy() { }

    /**
     * Executes task r in the caller's thread, unless the executor
     * has been shut down, in which case the task is discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}
DiscardOldestPolicy

就是把队列中阻塞最久的任务拿出来,再把新的任务加进去进行排队等待

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardOldestPolicy} for the given executor.
     */
    public DiscardOldestPolicy() { }

    /**
     * Obtains and ignores the next task that the executor
     * would otherwise execute, if one is immediately available,
     * and then retries execution of task r, unless the executor
     * is shut down, in which case task r is instead discarded.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll(); // 弹出队列头的任务
            e.execute(r);  // 加入新的任务
        }
    }
}
DiscardPolicy

这个就是什么都不做,看下面源码直接就是个空的

public static class DiscardPolicy implements RejectedExecutionHandler {
    /**
     * Creates a {@code DiscardPolicy}.
     */
    public DiscardPolicy() { }

    /**
     * Does nothing, which has the effect of discarding task r.
     *
     * @param r the runnable task requested to be executed
     * @param e the executor attempting to execute this task
     */
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

Q:线程池中的淘汰策略(3种情况)?

就是创建的线程现在没事做了,该线程该怎么办?

如果当前线程数量大于我们所设定的核心线程,比如200个线程在执行,我们所设定的核心线程为10,现在都闲下来了,我们会剩下10个线程,这10个线程不是最先创建的10个线程,而是经过CAS(Compare-And-Swap)竞争后所剩下的

这里分为正常执行完成和线程异常的淘汰策略:

正常执行完毕

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循环一旦结束当前线程就会被销毁 我们可以控制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); // aop前置事件,可以在任务执行前干一些事
                    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); // aop 后置事件,执行任务完之后干的事
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false; // 标记当前线程无异常正常退出
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }
private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

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

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

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; // 核心线程
			
            if ((wc > maximumPoolSize || (timed && timedOut)) // 第二次进来会执行这个方法
                && (wc > 1 || workQueue.isEmpty())) {
                // CAS竞争 所有空闲的线程进行竞争 成功这里就会return null 在上面的runWorker方法里这个线程就会被销毁
                // 剩下的几个线程就是核心线程,在这里无限阻塞,等待任务
                if (compareAndDecrementWorkerCount(c)) 
                    return null;
                continue;
            }

            try {
                Runnable r = timed ?
                     // 所有线程第一次都会来执行这个方法
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 这里就会执行超时阻塞
                    workQueue.take();			// 无限阻塞
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

扩展:

并发编程的灵魂:CAS机制详解 - 知乎 (zhihu.com)

CAS竞争是指在多线程环境中,多个线程同时使用CAS(Compare-And-Swap)操作更新同一个变量的情况。在这种情况下,只有一个线程能够操作成功,而其他线程都会更新失败。

当一个线程使用CAS操作时,它会比较变量的当前值和一个预期值,如果这两个值相等,则更新该变量的值。如果多个线程同时使用CAS操作更新同一个变量,那么只有一个线程能够成功地更新该变量的值,而其他线程的更新操作则会失败。

CAS竞争可能会导致线程间的竞争和冲突,因此需要谨慎处理。在某些情况下,可能需要使用其他同步机制来避免CAS竞争。

线程异常

比如某一个线程抛异常了,这个线程会被销毁然后新创建一个新线程

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

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

    tryTerminate();

    int c = ctl.get();
    if (runStateLessThan(c, STOP)) {
        if (!completedAbruptly) {
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        addWorker(null, false);  // 新创建一个线程
    }
}
Q:为什么要抛出异常

就是我们要对异常进行处理或者调试的话,不抛出异常会导致我们不容易去处理这些问题,在开发中很常用啊!

我们看官方的注解,这里我进行了简述和总结:

  1. 开始时,如果有初始任务,直接执行;否则,在池运行时从getTask获取任务。如果获取不到任务,线程就会因池状态或配置改变而退出。其他退出情况则由外部代码抛出异常导致,此时completedAbruptly为真,通常会导致线程被替换。
  2. 在执行任何任务之前,先获取锁以防止其他线程在任务执行过程中中断,并确保除非池正在停止,否则线程的中断状态不会被设置。
  3. 每个任务执行前会调用beforeExecute,如果抛出异常,将中断循环并设置completedAbruptly为真。
  4. 如果beforeExecute正常完成,就会执行任务并收集抛出的异常以供afterExecute处理。这里分别处理了运行时异常、错误(二者都会被我们捕获)以及其他任意Throwable。因为不能在Runnable.run内部重新抛出Throwable,所以会用错误来封装它们(传给线程的未捕获异常处理器)。任何抛出的异常都会导致线程终止。
  5. 任务执行完成后,调用afterExecute,如果它抛出异常,也会导致线程终止。根据JLS Sec 14.20,即使任务执行过程中抛出异常,此异常也将会生效。这种异常机制使得afterExecute和线程的未捕获异常处理器能够尽可能准确地了解用户代码遇到的问题。

可以看到第五条就是说能够尽可能准确地了解用户代码遇到的问题,方便调试和维护。

线程池关闭(2种方法)

假如我们执行了executor.shutdown()executor.shutdownNow() 方法,会修改线程池的状态,

shutdown 会等待所有执行中的任务和阻塞队列中的任务执行完毕后再结束

shutdownNow 不会等待阻塞队列中的任务结束,记住now 就是我要快点结束啊!

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess(); 
        advanceRunState(SHUTDOWN); // 修改线程池状态 在 getTask方法中会进行判断
        interruptIdleWorkers(); // 中断方法
        onShutdown(); 
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP); // 修改线程池状态 
        interruptWorkers(); // 中断方法
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private Runnable getTask() {
    boolean timedOut = false; 

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

        // 就是这里啊!会进行状态的判断是等队列为空在结束还是等现在线程执行完直接结束
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { 
            decrementWorkerCount();
            return null;
        }
        
        //...
    }
}
    mainLock.unlock();
}
tryTerminate();

}


```java
public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP); // 修改线程池状态 
        interruptWorkers(); // 中断方法
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}
private Runnable getTask() {
    boolean timedOut = false; 

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

        // 就是这里啊!会进行状态的判断是等队列为空在结束还是等现在线程执行完直接结束
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { 
            decrementWorkerCount();
            return null;
        }
        
        //...
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Kaiyue.zhao

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值