还不会线程池?JUC线程池源码级万字解析

news2024/11/24 9:59:59

线程池主要解决了两个问题:
第一个是当大量执行异步任务的时候提供较好的性能;在不使用线程池的时候,每次需要执行一个异步任务都需要新建一个 Thread 来进行,而线程的创建和销毁都是需要时间的,所以可以通过线程池来实现线程的复用,从而解决这个问题。
同时线程池也提供了一种资源限制和管理的手段,比如可以限制线程的个数、动态的增加线程等;ThreadPoolExecutor 保留了一些基本的统计数据,比如当前线程池完成的任务数目等。

1)介绍

1.1)使用案例

ThreadPoolExecutor 是 Java 中 java.util.concurrent包的一部分,用于管理线程池的实现。它提供了一种灵活的方法来 创建和管理线程池,以便有效地执行大量的任务。它允许开发人员配置线程池的各种参数,如核心线程数、最大线程数、线程闲置时间、任务队列等。简单来说,它就是一个内部维护了一个线程池的任务执行器(Executor)。

在正式开始阅读源码之前,先来看一下应该如何使用这个它来执行任

public class ThreadPoolExecutorSourceRead {
    public static void main(String[] args) {
    // 构造线程池执行器
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2,
                4,
                10,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(100));
        
        for (int i = 10; i >= 0; i--) {
            threadPoolExecutor.execute(() -> {
                try {
                    Thread.sleep(1000);
                    System.out.println("Hello");
                    System.out.println(threadPoolExecutor.isTerminated());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            });
        }

    // 关闭线程池
        threadPoolExecutor.shutdown();
        try {
            if (!threadPoolExecutor.awaitTermination(60, TimeUnit.SECONDS)) {
                threadPoolExecutor.shutdownNow();
            }
        } catch (InterruptedException e) {
            threadPoolExecutor.shutdownNow();
        }

    }
}

1.2)类图结构与继承关系

ThreadPoolExecutor 类的类图:

在这里插入图片描述

继承关系ThreadPoolExecutor 继承了 AbstractExecutorServiceAbstractExecutorService 又实现了 ExecutorService 接口,然后 ExecutorService 实现了 Executor 接口。
在这里插入图片描述

其中,Executor 接口是 Java 并发框架中的一个核心接口,用于 将任务的提交任务的执行解耦。它的设计目标是提供一种标准的方法来执行提交的任务,而不需要关心任务是如何执行的(例如,使用单个线程、线程池、异步调用等),这个接口是一个函数式接口,里面只有一个 execute(Runnable command); 方法。

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     */
    void execute(Runnable command);

常见的和 Executor 接口有关的类有以下几个:

  • Executors 工具类,里面提供了静态的工厂方法来创建常用的 Executor 实现
  • 本节中讲的 ThreadPoolExecutor 是该接口最常用的实现,用于基于创建的线程池来执行任务
  • SingleThreadExecutor , 使用单个工作线程执行任务,任务按提交顺序执行。
  • **ScheduledThreadPoolExecutor ,**支持任务定时和周期性执行的 Executor 实现

1.3)关键属性

说完了类的继承关系,下面按照类图中的顺序讲解一下 ThreadPoolExecutor需要了解的属性:

其中的成员变量 ctl 是一个 Integer 类型的原子变量,用来记录线程池的状态和线程池中的线程个数,类似于前面的 ReentrantReadWriteLock 使用一个变量来存储两个信息。

其中的 mainLock 是独占锁,用来控制新增 Worker 线程操作的原子性。termination 是这个锁对应的一个 Condition,线程调用**termination.awaitNanos(nanos)** 方法的时候会处于阻塞的情况;再通过**termination.signalAll();** 等方法来唤醒线程。

Worker 类实现了 Runnable 接口,是具体承载任务的对象,其代理了 Thread 线程,通过静态代理的方式代理了线程的 run() 方法,使得线程池管理线程更加方便。它继承 AQS 自己实现了简单的不可重入锁;其中 state=0 表示锁未被获取的状态,state=1 表示已经被获取的状态,state=-1 是 Worker 的默认状态,创建线程的时候设置初始状态会将 state 设置为 -1,将其设置为 -1 的原因后面会提到;其中的变量 firstTask 记录这个工作线程执行的第一个任务,thread 是具体执行任务的线程。

DefaultThreadFactory 是线程工厂,通过使用线程工厂提供的 newThread 方法可以便捷的创建线程

  • poolNumber 是一个静态的原子变量,用来统计线程工厂的个数,没当线程工厂被实例化之后,会使用 CAS 操作使得原子变量做一个递增操作。
  • threadNumber 用来记录每个线程工厂创建了多少线程,这两个值也作为线程池和线程名称的一部分。

2)预备知识

在正式开始源码的阅读之前,我们需要先了解一些预备知识。

2.1)线程池状态及转换方式

线程池有如下几种状态:

  • RUNNING:接受新任务并且处理阻塞队列中的任务。
  • SHUTDOWN:拒绝新任务并且处理阻塞队列中的任务。
  • TIDYING:所有任务都执行完成(包括阻塞队列中的任务)后当前线程池活动线程数为 0,将要调用 terminated 方法。
  • TERMINATED:终止状态, terminated() 方法调用完成以后的状态。

它们之间的转换方式是这样的:

  1. RUNNINGSHUTDOWN:显示的调用 shutdown() 方法。
  2. RUNNING 或者 SHUTDOWNSTOP:显示的调用 shutdownNow() 方法的时候。
  3. SHUTDOWNTIDYING:当线程池和任务队列都为空的时候。
  4. TIDYINGTERMINATED:当 terminated() 方法执行完成的时候。

2.2)线程池的参数

线程池的参数有以下几种:

  • corePoolSize:线程池核心线程个数
  • workQueue:用于保存等待执行的任务阻塞队列,比如基于数组的有界 ArrayBlockingQueue、基于链表的无界 LinkedBlockingQueue、最多只有一个元素的同步队列 SynchronousQueue 以及优先级队列 PriorityBlockingQueue 等。
  • maximumPoolSize:线程池的最大线程数量
  • ThreadFactory:创建线程的工厂
  • RejectedExecutionHandler:饱和策略,当任务队列满并且线程个数达到最大容量的时候采取的策略,比如 AbortPolicy 抛出异常、CallerRunsPolicy 使用调用者所在的线程来运行任务、DiscardOldestPolicy 调用 poll 丢弃一个任务,执行当前的任务、以及 DiscardPolicy 默默丢弃不抛弃异常。
  • keeyAliveTime:存活时间。如果当前线程池中的线程数量比核心线程数量多,并且是闲置的状态,则这些闲置的线程存活的最大时间
  • TimeUnit:存活时间的时间单位

2.3)线程池的类型

线程池的类型有以下几种,可以通过 Executors 工具类提供的方法快速的创建

  • newFixedThreadPool:创建一个核心线程个数和最大线程个数都为 nThreads 的线程池,并且阻塞队列的长度为 Integer.MAX_VALUE。keeyAliveTIme 为 0,说明只要线程个数比核心线程个数多就执行回收的操作
    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
    // 使用自定义线程创建工厂
    public static ExecutorService newFixedThreadPool(int nThreads, 
		    ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
  • newSingleThreadExecutor:常见一个核心线程个数和最大线程个数都为 1 的线程池,并且阻塞队列的长度为 Integer.MAX_VALUE。且 keeyAliveTime = 0,即只要线程个数比核心线程数多就回收。
    public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
    // 使用线程工厂创建
    public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>(),
                                    threadFactory));
    }
  • newCachedThreadPool:创建一个按需创建线程的线程池,初始的线程个数为 0,最多的线程个数为 Integer.MAX_VALUE,并且阻塞队列为同步队列。它的线程空闲存活时间为 60s。加入同步队列的任务会被马上执行,且同步队列中最多只有一个任务。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
    // 使用线程工厂创建
    public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>(),
                                      threadFactory);
    }

2.4)线程池的执行逻辑

线程池中任务的执行是依赖于 Worker 实例化对象的,它被创建后会存储在

private final HashSet<Worker> workers = new HashSet<Worker>();

中,Worker 是一个实现了 Runnable 接口的类,它的内部存储着一个真正执行任务的线程,它的创建方式是这样的:

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this); // 创建线程
        }
        
        Thread newThread(Runnable r);

将 this 也就是 Worker 对象本身传递给线程工厂来创建,当通过 start 方法启动这个线程的时候,其实执行的就是 Worker 中重写的 run 方法。Worker 类是实际执行任务的类,它的 run() 方法的逻辑就是通过循环不断的从任务队列 workQueue 中获取任务,然后执行。

线程池就是维护根据上面提到的,诸如核心线程数,存活时间等参数来维护 workers,实时的增加或者删除其中的线程;通过将任务加入到 workQueue 队列中来让这些 worker 来执行这些任务。

3)源码分析

3.1)线程池执行任务

这个方法的作用是根据线程池当前的状态来决定让这个任务立刻执行、添加到线程池还是。执行拒绝策略

ThreadPoolExecutor 的实现是一个生产消费模型,当用户添加任务到线程池相当于生产者生产元素,而 Worker 来执行任务相当于消费元素;这个方法的执行逻辑在源码的注释中已经写的很清楚了,下面是翻译后的内容:

  1. 如果运行的线程少于 corePoolSize,尝试启动一个新线程,并将给定的命令作为其第一个任务。调用 addWorker 会原子地检查运行状态和工作线程数量,从而防止错误报警导致不应增加线程的情况,通过返回 false 来阻止这种情况。
  2. 如果任务可以成功排队,那么我们仍然需要再次确认是否应该添加一个线程(因为自上次检查以来现有的线程可能已经终止)或者线程池在进入该方法之后已经关闭。因此,我们会重新检查状态,并在必要时回滚任务的排队操作(如果线程池已经停止),或者如果没有线程则启动一个新线程。
  3. 如果我们不能将任务排队,那么我们会尝试添加一个新线程。如果添加失败,我们就知道线程池已经关闭或已饱和,因此拒绝该任务。
    public void execute(Runnable command) {
		    // (1)检查传入的参数,如果为 null 会抛出 NPE 异常
        if (command == null)
            throw new NullPointerException();
            
        // (2)获取当前线程池的状态 + 线程个数的组合值  
        int c = ctl.get();
        
        // (3)检查线程池的个数是否小于 corePoolSize,如果小于则开启新的线程
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        
        // (4)如果线程池处于 Running 状态,添加任务到阻塞队列
        if (isRunning(c) && workQueue.offer(command)) {
		        // (4.1)二次检查
            int recheck = ctl.get();
            // 如果线程池的状态不是 RUNNING 则删除队列中的任务,执行拒绝策略
            if (! isRunning(recheck) && remove(command))
                reject(command);
            // (4.2)如果当前线程池为空,则添加一个线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        
        // (5)阻塞队列满了,尝试添加新的线程,如果失败了就执行拒绝策略
        else if (!addWorker(command, false))
            reject(command);
    }

上面需要特别注意的是代码(5)的执行时机,也就是当代码 (4)执行失败的处于什么状态

此时向任务队列中添加任务失败,或者是线程池不处于 RUNNING 状态(除了 RUNNING 状态以外都无法添加任务或需要执行拒绝策略),此时执行这段代码来尝试重新开启一个线程来执行任务,如果此时仍然添加失败,我们就知道线程池已经关闭或已饱和,因此拒绝该任务。

3.2)添加 Worker

这个方法是用来添加一个新的 Worker 对象到 workers 中,参数为初始任务(firstTask)、是否是核心线程(core)。

在添加 Worker 的时候需要保证并发安全性,且需要实时的监测线程池的状态,并且还含有执行任务,修改参数等代码,所以长度会很长,这里分成两个部分来讲解;先来看第一部分,这一部分通过 CAS 操作尝试去修改 Worker 的个数,当修改成功后才会去执行真正的实例化代码。

    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 1)检查队列是否在必要的时候为空
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

						// 2)在循环中通过 CAS 来增加线程的个数
            for (;;) {
                int wc = workerCountOf(c);
                // 2.1)如果线程数目超过指定的上限,返回 false
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                // 2.2)使用 CAS 来增加线程的个数
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 2.3)如果 CAS 失败了,先去检查线程池的状态是否变化,
                //     如果变化则跳到外层循环重新获取线程池状态,否则循环继续尝试 CAS。
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
            
            // 。。。 第二段代码
        }

代码(1)是检测线程池状态的代码,通过逻辑运算,可以将其转换成以下的格式

s >= SHUTDOWN && 
							(rs != SHUTDOWN ||
							firstTask != null ||
							workQueue.isEmpty())

这段逻辑的含义是这样的:

  • 如果当前线程池状态 s 大于或等于 SHUTDOWN(即线程池已经开始关闭或已经关闭)。
  • 然后进一步检查以下情况之一:
    • 重新检查的状态 rs 不是 SHUTDOWN,表示线程池的状态可能已经改变。
    • firstTask 不为空,意味着有一个新的任务尝试提交。
    • 任务队列是空的。

当上述条件都满足时,通常表示线程池不应接受新的任务,因此会拒绝任务的提交。

然后,线程在循环中不断通过 CAS 操作来尝试修改 WorkCount 的值(ctl 的一部分),如果修改成功,则继续执行下一部分代码。

// 3)CAS 成功了
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
		        // 3.1)创建 worker
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
		            // 3.2)加锁,防止并发问题
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
                    int rs = runStateOf(ctl.get());
										// 3.3)重新检查线程池的状态,线程池处于 SHUTDOWN 方法会停用
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        // 如果线程被启用,线程工厂创建的线程出了问题,直接抛出异常
                        if (t.isAlive())
                            throw new IllegalThreadStateException();
                        // 3.4)添加一个任务执行单位
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //  3.5)如果添加任务成功就执行任务
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

这一部分代码是成功通过 CAS 添加 Worker 的个数后执行的,但是现在任务还没有正式开始执行。在将 worker 添加到 workers 之前通过独占锁保证了线程安全。

最终,当添加成功的时候,就会启动工作线程。

3.3)工作线程执行

当用户提交任务到线程池之后,是通过 Worker 来执行的,Worker 是 ThreadPoolExecutor 中的一个内部类,它的构造方法是这样的:

        Worker(Runnable firstTask) {
            setState(-1); // 在调用 runWorkker 方法前禁止中断
            this.firstTask = firstTask;
            // 通过线程工厂来创建一个线程
            this.thread = getThreadFactory().newThread(this);
        }

在构造方法的时候设置 Worker 的状态为 -1,是为了避免 Worker 在调用 runWorker 方法之前被中断,当其他线程调用线程池 shutdownNow 的时候,如果 Worker 状态大于等于零则会被中断。这里设置了 -1 就不会被中断了。

而当执行 runWorker 方法的时候,会将其 state 设置为 0,此时就可以被中断了。

Worker 类实现了 Runnable 接口,也就是实现了 run() 方法,这个方法的内部调用的就是 runWorker() 方法来完成线程执行逻辑。

        /** Delegates main run loop to outer runWorker  */
        public void run() { runWorker(this); }

下面来看一下 runWorker 方法的具体实现:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 允许 Worker 被中断
        boolean completedAbruptly = true;
        try {
        // 循环执行任务,有任务的时候会一直循环
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // (1)
                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() 方法来执行(task 也是实现了 Runnable 接口的对象),在执行任务之前会 Worker 加上锁,这样说为了避免在执行任务期间,其他线程调用 shutdown 方法导致正在执行的任务被中断,shutdown 方法的逻辑的只中断被阻塞挂起的线程,这个在后面分析源码的时候会提到。

上面的 (1)代码是处理线程池的停止和线程的中断逻辑的,它的判断逻辑是这样的:

这个条件判断分为两部分:

  • runStateAtLeast(ctl.get(), STOP)检查线程池的状态是否至少是STOP状态,这意味着有请求停止线程池。
  • (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))检查当前线程是否已被中断,并且线程池状态也是STOPThread.interrupted()会清除当前线程的中断状态并返回之前中断的状态。 如果以上任意条件满足,并且工作线程wt尚未被中断(!wt.isInterrupted()),则执行下一行的中断操作。

当线程在循环中结束后,最终执行会清理工作,调用 processWorkerExit,其代码如下:

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly)
            decrementWorkerCount();

				// 统计当前线程完成任务的个数,并且从工作集中删除当前的 Worker
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

				// 尝试设置线程状态为 TERMINATED,当前是 SHUTDOWN 状态并且工作
				// 队列为空的时候或者当前为 STOP 状态,线程池中没有活动线程
        tryTerminate();

				// 如果当前线程个数小于核心数目,增加新的 Worker
        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);
        }
    }

代码中先加入全局锁,然后统计线程池完成任务的个数,最后删除 Worker 对象。

然后尝试将线程池的状态设置为 TERMINATED,最终检测当前线程池中的线程个数是否小于核心线程数,如果是则会新增一个工作线程,并将其 firstTask 设置为 null。

3.4)shutdown 操作

调用 shutdown 方法后,线程池不会再接受新的任务了,但是工作队列中的任务还是会执行的,该方法会立刻返回,不会等待队列任务完成再返回。

    public void shutdown() {
    // 锁,注意这个锁是 ThreadPoolExecutor 中的锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
       
        try {
		        // 检查当前线程是否有权限执行
            checkShutdownAccess();
            
            // 设置状态为 SHUTDOWN,如果已经是则直接返回
            advanceRunState(SHUTDOWN);
            
            // 给 Workers 设置中断标志
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        
        // 尝试中断
        tryTerminate();
    }

其中比较关键的方法是 advanceRunState() 方法和 interruptIdleWorkers() 两个方法,下面来分别看一下它们的实现:

    private void advanceRunState(int targetState) {
        for (;;) {
            int c = ctl.get();
            // 当前状态已经比 SHUTDOWN 还要高,也就是以下三种情况
            // STOP、TIDYING、TERMINATED
            if (runStateAtLeast(c, targetState) ||
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
                break;
        }
    }
    
    // 用于判断线程池的运行状态(c)是否至少达到了给定的状态级别(s)
    private static boolean runStateAtLeast(int c, int s) {
        return c >= s;
    }

上面的方法中先判断当前状态是否已经达到了 SHUTDOWN,如果是直接返回,否则就通过 CAS 操作将其状态设置为 SHUTDOWN。

    private void interruptIdleWorkers(boolean onlyOne) {
		    // 上锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                // 如果线程没有被中断,并且获取锁成功,也就是说这个线程没有执行任务
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
		                    // 尝试中断线程
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                    // unlock worker,将其 state 设置为 0
                        w.unlock();
                    }
                }
                if (onlyOne)
                // 如果只需要中断一个线程的话,释放锁
                    break;
            }
        } finally {
        // 释放锁
            mainLock.unlock();
        }
    }

上面的方法接收一个布尔值参数 onlyOne 表明中断一个线程还是全部,然后在循环工作集,中断未被中断的线程,在中断线程的时候会使用 Worker 的独占锁,而正在执行任务的线程会持有自己的锁,无法被释放,所以这里其实只中断了未执行任务的锁。

3.5)shutdownNow() 方法

看完了不会中断执行任务线程的 shutdown 方法, shutdownNow 方法则会中断所有线程,无论它们是否在执行任务。

这个方法与前面的 shutdown 方法的区别就是调用的方法不通,在这里中断线程调用的是 interruptWorkers() ,会中断所有的线程。

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

这里不会去尝试获取线程的锁,而是直接执行中断的方法,这里如果线程的 state 小于零(未启动的时候初始值为 -1),此时不会去中断线程。

    private void interruptWorkers() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                w.interruptIfStarted();
        } finally {
            mainLock.unlock();
        }
    }
    
		void interruptIfStarted() {
		      Thread t;
		      if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
		          try {
		              t.interrupt();
		          } catch (SecurityException ignore) {
		          }
		      }
		}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1804114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据集笔记:DGraph 大规模动态图数据集

dgraph-web (xinye.com) 1 数据集介绍 DGraph 是一个有向无权的动态图&#xff0c;包含超过 370 万个节点以及 430 万条动态边DGraph 中的节点表示金融借贷用户&#xff0c;有向边表示紧急联系人关系&#xff0c;每个节点包含脱敏后的属性特征&#xff0c;以及表示是否为金融…

Java 的循环

Java 有三种循环&#xff1a;for&#xff0c;while&#xff0c;do while。 for 基本语法&#xff1a; for (循环变量初始化; 循环条件; 循环变量迭代){循环语句; }程序示例&#xff1a; public static void main(String[] args) {for (int i 0, j 0; i < 3; i, j--) {…

45-2 waf绕过 - XSS 绕过WAF方法

环境准备: 43-5 waf绕过 - 安全狗简介及安装-CSDN博客然后安装pikachu靶场:构建完善的安全渗透测试环境:推荐工具、资源和下载链接_渗透测试靶机下载-CSDN博客打开pikachu靶场 http://127.0.0.1/pikachu-master/vul/xss/xss_reflected_get.php 使用常见payload被安全狗拦截…

树莓派 AI 套件,售价 70 美元

系列文章目录 前言 2024 年 6 月 4 日 Naush Patuck 如果您曾想在您的 Raspberry Pi 5 上尝试神经网络、人工智能和机器学习&#xff0c;我们为您准备了完美的产品&#xff1a;Raspberry Pi AI Kit。AI Kit 是与 Hailo 合作开发的&#xff0c;它提供了一种便捷的方法&…

STM32F103C8T6基于HAL库移植uC/OS-III

文章目录 一、建立STM32CubeMX工程二、移植1、 uC/OS-III源码2、移植过程 三、配置相关代码1、bsp.c和bsp.h2、main.c3、修改启动代码4、修改app_cfg.h文件5、修改includes.h文件6、修改lib_cfg.h文件 四、编译与烧录总结参考资料 学习嵌入式实时操作系统&#xff08;RTOS&…

OpenStack学习笔记之三:用软件定义的理念做安全

第3章 用软件定义的理念做安全 1.不进则退&#xff0c;传统安全回到“石器时代” 1.1 企业业务和IT基础设施的变化 随着企业办公环境变得便利&#xff0c;以及对降低成本的天然需求&#xff0c;企业始终追求IT集成设施的性价比、灵活性、稳定性和开放性。而云计算、移动办公…

HAL STM32F1 通过查表方式实现SVPWM驱动无刷电机测试

HAL STM32F1 通过查表方式实现SVPWM驱动无刷电机测试 &#x1f4cd;相关篇《基于开源项目HAL STM32F4 DSP库跑SVPWM开环速度测试》 ✨针对STM32F1系列&#xff0c;硬件上没有可用的浮点单元&#xff08;FPU&#xff09;&#xff0c;为了实现特定函数的浮点运算快速计算&#xf…

独立游戏之路 -- TapTap广告收益损失和常见问题

一个操作带来的TapTap广告收益损失 一,收益损失1.1 广告入口1.2 损失对比二,常见问题2.1 有展现量没有预估收益 /eCPM 波动大?2.2 新建正式媒体找不到预约游戏2.3 聚合模式由于没有回传 oaid 无数据2.4 每日观看次数限制是否有限制一,收益损失 1.1 广告入口 TapTap广告联…

针对业务系统的主备容灾实战原理-基础版

1、前言 本文主要在于介绍&#xff1a;通过系统的实时容灾功能模块&#xff0c;针对用户云计算中关键业务系统的主备容灾方案原理。 涉及到的技术能力、运维能力要求偏高&#xff0c;遂本文尽量将容灾原理讲解清楚。需要用到的云计算能力包括&#xff1a;计算机操作系统(Linu…

初入阿里云,上手走一波

初入阿里云&#xff0c;上手走一波 一阶&#xff1a;ECSMysqlDMS安装Mysql初始化MysqlMysql操作DMS管理Mysql 二阶&#xff1a;ECSOSS远程连接ECSOSS控制台其他图片服务 三阶&#xff1a;更多搭配操作 可以说个人在日常使用过程中&#xff0c;操作最多的阿里云产品就是阿里云服…

【el-tooltips改造】Vue实现文本溢出才显示el-tooltip,否则不显示el-tooltips

实现原理&#xff1a; 使用disabled属性控制el-tooltip的content显示与隐藏&#xff1b; 目标&#xff1a; 1行省略、多行省略、可缩放页面内的文本省略都有效。 实现方式&#xff1a; 1、自定义全局指令&#xff0c;tooltipAutoShow.js代码如下&#xff08;参考的el-table中的…

【Python学习路线(课程大纲+Python视频教程+下载地址)_python 教程下载。】

目前Python已经成为最受欢迎的程序设计语言之一。Python的设计哲学是“优雅”、“明确”、“简单”。 学习Python具有多重显著的好处。首先&#xff0c;Python的语法简洁易读&#xff0c;降低了编程的入门门槛&#xff0c;使初学者能够更快地掌握编程的基本概念。其次&#xff…

自定义拦截器

大家好&#xff0c;这里是教授.F 前菜&#xff1a; 拦截器是由springmvc来接管的&#xff0c;过滤器使用服务器来接管的。 ● 自定义拦截器的三个方法[自定义的拦截器必须实现 HandlerInterceptor 接口] 1. preHandle()&#xff1a;这个方法在业务处理器处理请求之前被调用&…

SVG不保持横纵比,完全由设置宽高任意拉伸填充

想要通过变形伸缩 填充元素的方式使用 svg&#xff0c;试了很多办法&#xff0c;终于找到的。 之前试过img形式显示svg虽然合适变形伸缩&#xff0c;但不能设置颜色。下面是正确效果的使用说明。 在源码svg中加 preserveAspectRatio"none" <svg width"…

访问网站时IP被阻止?原因及解决方法

在互联网上&#xff0c;用户可能会面临一个令人困扰的问题——当尝试访问某个特定的网站时&#xff0c;却发现自己的IP地址被该网站屏蔽。 IP地址被网站屏蔽是一个相对常见的现象&#xff0c;而导致这种情况的原因多种多样&#xff0c;包括恶意行为、违规访问等。本文将解释IP地…

Linux环境在非root用户中搭建(java-tomcat-redis)

注: 本文在内网(离线)环境&#xff0c;堡垒机中搭建&#xff0c;服务器不同可能有所差异&#xff0c;仅供参考 本文安装JDK-20.0.1版本&#xff0c;apache-tomcat-10.1.10版本&#xff0c;redis-6.2.15版本 本文服务器IP假设&#xff1a;192.168.88.133 root用户创建子用户并…

vue3 实现自定义指令封装 --- 通俗易懂

1、局部自定义指令 1.1 在<script setup>定义组件内的指令&#xff0c;任何以v开头的驼峰式命名的变量都可以被用作一个自定义指令 <template><div><h3>使用自定义指令</h3><div>########################## start 局部自定义指令</d…

哈希桶封装unordered_map、unordered_set

哈希桶源代码 我们将由下列的哈希桶来模拟封装STL库中的unordered_map和unordered_set 注意&#xff1a;为了实现封装unordered_map和unordered_set&#xff0c;我们需要对下列源码进行优化。 //哈希桶 namespace hashbucket {template<class K,class V>struct HashNo…

【YOLOv7改进系列】简化YOLOv7-tiny池化层,便于引入改进的池化层

〇、前言 相比YOLOv5/v7&#xff0c;除了YOLOv5n外&#xff0c;YOLOv7tiny的参数量较小&#xff0c;效果往往也相较YOLOv5n高上不少&#xff0c;又近来博主打算改进yolov7-tiny文件&#xff0c;但苦于其池化层部位是直接写在yaml中的&#xff0c;修改极为不便&#xff0c;因此…

使用安装包安装tomcat

Apache Tomcat 是一个开源的 Java 服务器&#xff0c;用于运行 Java Servlet、JavaServer Pages (JSP) 和相关的 Java 平台技术。它是一个轻量级的、灵活的容器&#xff0c;用于在 Java 环境中部署和管理 Web 应用程序。 以下是 Tomcat 的一些主要特点和功能&#xff1a; 1.Ser…