[Programación concurrente] Envío de tareas ThreadPoolExecutor y proceso de detención e implementación subyacente [Edición de exploración para novatos]


Código de prueba:

// 创建只含有单个线程的线程池
ExecutorService executor = Executors.newSingleThreadExecutor();
// 提交任务
executor.submit(()->{
    
    
    System.out.println("C");
});
// 优雅停机
executor.shutdown();

1. Envío de tareas ThreadPoolExecutor

Veamos el método de envío.

// AbstractExecutorService
public Future<?> submit(Runnable task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
  • newTaskFor: Construya Runnable como tarea FutureTask
  • ejecutar: ejecutar la tarea
public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    // 1、线程数量小于核心线程,则添加Worker
    if (workerCountOf(c) < corePoolSize) {
    
    
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    // 2、插入任务到队列
    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);
    }
    // 3、队列也满了,则添加非核心work
    else if (!addWorker(command, false))
        // 4、添加非核心Worker失败则"拒绝"
        reject(command);
}

De lo anterior, podemos ver que el principio de procesamiento del grupo de subprocesos es: Número de subprocesos principales> Cola> Subprocesos no principales> Política de rechazo

1. Primero agregue subprocesos centrales para su procesamiento.

2. Si no hay suficientes subprocesos principales, agréguelos a la cola.

3. Si la cola no es suficiente, agregue "subprocesos no principales"

4. Si no hay suficientes subprocesos no principales, se implementará una política de rechazo.

2. Estado del grupo de subprocesos [Esta parte es la parte difícil]

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    
    // 核心变量:该变量前3位表示"状态",后29为表示线程数量
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    
    // 该变量控制ctl变量用多少bit位表示状态【bit位的分界线】
    private static final int COUNT_BITS = Integer.SIZE - 3;
    
    // 表示线程的最大容量【即0001 1111 1111...】
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
    
    // 以下变量用前3位控制线程池的状态
    private static final int RUNNING    = -1 << COUNT_BITS; // [即111]
    private static final int SHUTDOWN   =  0 << COUNT_BITS; // [即000]
    private static final int STOP       =  1 << COUNT_BITS; // [即001]
    private static final int TIDYING    =  2 << COUNT_BITS; // [即010]
    private static final int TERMINATED =  3 << COUNT_BITS; // [即011]
    
    // 计算ctl变量的前3位,表示“状态”
    private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }
    // 计算ctl变量的后29位,表示"工作线程数量"
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }
    private static int ctlOf(int rs, int wc) {
    
     return rs | wc; }
}
  • ctl : es la abreviatura de control. Es la variable central del grupo de subprocesos. Esta variable int contiene tanto el estado como el número de subprocesos.
  • COUNT_BITS: la línea divisoria de los bits de la variable ctl
  • CAPACIDAD: Indica el número máximo de subprocesos en el grupo de subprocesos.
  • Varios estados: use los primeros 3 bits de ctl para representar el estado
  • Métodos runStateOf, workCountOf: calcula el estado y el número de subprocesos

2.1 addWorker agrega hilo de trabajo

private boolean addWorker(Runnable firstTask, boolean core) {
    
    
    // <1>
    retry:
    for (;;) {
    
    
        int c = ctl.get();
        // 获取状态
        int rs = runStateOf(c);

        // <1.1> 状态判断
        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;
            //增加c变量的线程数
            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
        }
    }

    // <2>
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    
    
        w = new Worker(firstTask); // 创建Worker
        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;
}

El código addWorker se divide en 2 partes

  • Mantener variables ctl (en la <1>sección)

Utilice cas para agregar uno al valor de posición de la variable ctl que indica el número de subprocesos.

Aquí <1.1>siento que la forma de escribir es bastante confusa para juzgar el estado.

if (rs >= SHUTDOWN && !(rs == SHUTDOWN && firstTask == null && !workQueue.isEmpty()))
    return false;

Para que la condición general sea verdadera, rs >= SHUTDOWN es verdadero y el juicio de los siguientes corchetes es falso. Hay tres situaciones para los siguientes corchetes:

1. Si rs == SHUTDOWN es falso ( las condiciones implícitas son: rs >= SHUTDOWN es verdadero ) .

rs == APAGADO, entonces rs puede tener uno de los valores de parada, ordenamiento y terminación, es decir, el grupo de subprocesos ha finalizado, lo que significa que no es necesario agregar subprocesos de trabajo en el estado terminado.

2. Si firstTask == null es falso ( las condiciones implícitas son: rs >= SHUTDOWN, rs == SHUTDOWN es verdadero ) .

El grupo de subprocesos está en estado de apagado y firstTask == null es falso, lo que significa que no es necesario agregar subprocesos de trabajo en estado de apagado.

3. Si workQueue.isEmpty() es verdadero ( se establece la condición implícita: rs=SHUTDOWN, firstTask == null ) .

Si el grupo de subprocesos está en estado cerrado, la tarea enviada también es nula y la cola está vacía, no es necesario agregar un subproceso de trabajo. [Si la cola no está vacía, ¿aún es necesario agregar un Trabajador?

Nota: ¿Es realmente necesaria la tercera condición? ? ? Debido a que algunas tareas en workQueue consumen mucho tiempo, ¿agregar trabajadores acelera el procesamiento de las tareas en workQueue?

El propósito de un juicio condicional tan complejo es que se seguirán agregando subprocesos de trabajo incluso si se produce el cierre en determinadas condiciones, lo que no significa que no sea necesario agregar subprocesos de trabajo mientras se produzca el cierre.

Crítica : No hay absolutamente ninguna necesidad de complicar tanto las condiciones, solo se puede decir que el autor persigue lo último. Es absolutamente posible devolver falso siempre que el estado >= apagado. ¿Y cómo podría suceder firstTask=null? Este método es privado.

¿Cuáles son las condiciones finales ? ①rs>=shutdown y ②rs=shutdown y ③firstTask=null y ④workQueue no está vacío. Es decir, cuando se cumplen estas condiciones, aún se pueden agregar subprocesos de trabajo.

  • Agregar hilo de inicio (en la <2>sección)

1. Crear trabajador

La variable firstTask representa la primera tarea en el Worker.

Cada trabajador representa un hilo de trabajo.

2. Agregar trabajador a los trabajadores

Todos los subprocesos son mantenidos uniformemente por la variable de trabajadores del grupo de subprocesos.

3. El proceso de agregar subprocesos debe estar " bloqueado ". Después de agregar el subproceso, llame t.startal método para iniciar el subproceso.

2.2 Trabajador de clase interna

/**
 * Worker类大体上管理着运行线程的中断状态 和 一些指标
 * Worker类投机取巧的继承了AbstractQueuedSynchronizer来简化在执行任务时的获取、释放锁
 * 这样防止了中断在运行中的任务,只会唤醒(中断)在等待从workQueue中获取任务的线程
 * 解释:
 *   为什么不直接执行execute(command)提交的command,而要在外面包一层Worker呢??
 *   主要是为了控制中断....重点主要原因啦
 *   用什么控制??
 *   用AQS锁,当运行时上锁,就不能中断,TreadPoolExecutor的shutdown()方法中断前都要获取worker锁
 *   只有在等待从workQueue中获取任务getTask()时才能中断
 *
 * worker实现了一个简单的不可重入的互斥锁,而不是用ReentrantLock可重入锁
 * 因为我们不想让在调用比如setCorePoolSize()这种线程池控制方法时可以再次获取锁(重入)
 * 解释:
 *   setCorePoolSize()时可能会interruptIdleWorkers(),在对一个线程interrupt时会要w.tryLock()
 *   如果可重入,就可能会在对线程池操作的方法中中断线程,类似方法还有:
 *   setMaximumPoolSize()
 *   setKeppAliveTime()
 *   allowCoreThreadTimeOut()
 *   shutdown()
 * 此外,为了让线程真正开始后才可以中断,初始化lock状态为负值(-1),在开始runWorker()时将state置为0,而state>=0才可以中断
 * 
 * Worker继承了AQS,实现了Runnable,说明其既是一个可运行的任务,也是一把锁(不可重入)
 */
private final class Worker extends AbstractQueuedSynchronizer implements Runnable{
    
    
    final Thread thread; //利用ThreadFactory和 Worker这个Runnable创建的线程对象
     
    Runnable firstTask;
     
    volatile long completedTasks;
 
    Worker(Runnable firstTask) {
    
    
        //设置AQS的同步状态private volatile int state,是一个计数器,大于0代表锁已经被获取
        setState(-1);
        // 在调用runWorker()前,禁止interrupt中断,
        //在interruptIfStarted()方法中会判断 getState()>=0
        this.firstTask = firstTask;
        this.thread = getThreadFactory().newThread(this); 
        //根据当前worker创建一个线程对象
       //当前worker本身就是一个runnable任务,也就是不会用参数的firstTask创建线程,
       //而是调用当前worker.run()时调用firstTask.run()
    }
 
    public void run() {
    
    
        runWorker(this); 
        //runWorker()是ThreadPoolExecutor的方法
    }
 
    // Lock methods
    // The value 0 represents the unlocked state. 0代表“没被锁定”状态
    // The value 1 represents the locked state. 1代表“锁定”状态
 
    protected boolean isHeldExclusively() {
    
    
        return getState() != 0;
    }
 
    /**
     * 尝试获取锁
     * 重写AQS的tryAcquire(),AQS本来就是让子类来实现的
     */
    protected boolean tryAcquire(int unused) {
    
    
        //尝试一次将state从0设置为1,即“锁定”状态,
        //但由于每次都是state 0->1,而不是+1,那么说明不可重入
        //且state==-1时也不会获取到锁
        if (compareAndSetState(0, 1)) {
    
    
            setExclusiveOwnerThread(Thread.currentThread()); 
            //设置exclusiveOwnerThread=当前线程
            return true;
        }
        return false;
    }
 
    /**
     * 尝试释放锁
     * 不是state-1,而是置为0
     */
    protected boolean tryRelease(int unused) {
    
    
        setExclusiveOwnerThread(null); 
        setState(0);
        return true;
    }
 
    public void lock()        {
    
     acquire(1); }
    public boolean tryLock()  {
    
     return tryAcquire(1); }
    public void unlock()      {
    
     release(1); }
    public boolean isLocked() {
    
     return isHeldExclusively(); }
 
    /**
     * 中断(如果运行)
     * shutdownNow时会循环对worker线程执行
     * 且不需要获取worker锁,即使在worker运行时也可以中断
     */
    void interruptIfStarted() {
    
    
        Thread t;
        //如果state>=0、t!=null、且t没有被中断
        //new Worker()时state==-1,说明不能中断
        if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
            try {
    
    
                t.interrupt();
            } catch (SecurityException ignore) {
    
    
            }
        }
    }
}

La clase Worker en sí no solo implementa Runnable, sino que también hereda AbstractQueuedSynchronizer, por lo que es una tarea ejecutable y puede lograr el efecto de bloqueo.

Hay varias cuestiones principales:

  • ¿Por qué hay un candado?

Definitivamente no es para controlar la concurrencia, porque solo hay un hilo. Principalmente para controlar las interrupciones . Con el bloqueo AQS, cuando el bloqueo se está ejecutando, no se puede interrumpir. El método Shutdown() de TreadPoolExecutor debe adquirir el bloqueo del trabajador antes de interrumpir. Solo se puede interrumpir mientras se espera obtener la tarea getTask() de workQueue.

  • ¿Dónde se refleja la interrupción del control?

1. El estado inicial de AQS es -1. La interrupción () no está permitida en este momento. La interrupción solo puede ocurrir cuando se inicia el subproceso de trabajo, se ejecuta runWoker () y el estado se establece en 0.

No se permiten interrupciones:

  1. Al cerrar () el grupo de subprocesos, cada trabajador tryLock() se bloqueará, y el método tryAcquire() de AQS de la clase Worker es corregir el estado de 0->1, por lo que tryLock() falla cuando el estado inicial es state=-1, no hay manera de interrumpir la interrupción()
  2. Al cerrar el grupo de subprocesos Now (), no use tryLock () para bloquear, sino que llame a Workers.interruptIfStarted () para finalizar al trabajador. InterruptIfStarted () también tiene la lógica de que el estado> = 0 puede interrumpir y el estado inicial = -1

2. Para evitar que el trabajador en ejecución sea interrumpido en determinadas circunstancias, runWorker() bloqueará() cada vez que ejecute una tarea, y operaciones como el apagado() que pueden terminar al trabajador deben obtener el bloqueo del trabajador primero. , lo que evita la interrupción de subprocesos en ejecución. El AQS implementado por Worker es un bloqueo no reentrante, para ingresar otros métodos que requieren bloqueo después de obtener el bloqueo del trabajador.

  • ¿Hay alguna interrupción de respuesta?

Es obvio que ThreadPoolExecutor no respondió a las interrupciones (es decir, no detectó ni manejó las interrupciones). Las interrupciones que no responden y las interrupciones controladas son dos cosas diferentes . El autor no sabe por qué se necesita una capa extra de Trabajo para controlar las interrupciones.

  • ¿Se puede hacer sin interrupciones ni bloqueos?

Suposición personal: está perfectamente bien si escribimos el código nosotros mismos, pero este es un código escrito por un maestro, por lo que no está permitido.

  • ¿Por qué debería heredar AQS usted mismo en lugar de utilizar ReentrantLock?

ReentrantLock es reentrante, mientras que ThreadPoolExecute requiere no reentrante

2.3 runWorker(): ejecutar tareas

Eche un vistazo a cómo el trabajador realiza las tareas

// java.util.concurrent.ThreadPoolExecutor.Worker
public void run() {
    
    
    runWorker(this);
}
final void runWorker(Worker w) {
    
    
    Thread wt = Thread.currentThread();
    // <1>
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
    
    
        // <2> 获取任务【重点】
        while (task != null || (task = getTask()) != null) {
    
    
            // <3> 上锁,不是为了防止并发执行任务,为了在shutdown()时不终止正在运行的worker
            w.lock();
            // <4> 极端判断条件是:由Running或shutdown状态,突然变成stop状态
            if ((runStateAtLeast(ctl.get(), STOP) || 
                 (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
                && !wt.isInterrupted())
                wt.interrupt();
            // <5>
            try {
    
    
                // <5.1>
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
    
    
                    // <5.2>
                    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 {
    
    
                    // <5.3>
                    afterExecute(task, thrown);
                }
            } finally {
    
    
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        // <6> completedAbruptly变量表示是否是"暴力结束线程",执行到此处说明线程是因为taskTask返回为null
        completedAbruptly = false;
    } finally {
    
    
        // <7> worker线程的退出【重点】
        processWorkerExit(w, completedAbruptly);
    }
}
  • en <1>_

La tarea agregada al crear el Trabajador es la primera tarea (firstTask)

  • en <2>_

Llame a la tarea getTaskdesde workQueuebuscar. Para obtener más información, consulte la explicación detallada del método getTask.

  • ¿Por qué está <3>bloqueado?

Es para controlar las interrupciones , controlar, controlar, no responder a las interrupciones, el grupo de subprocesos no responde a las interrupciones.

  • en <4>_

¿Cuáles son las condiciones extremas en este momento: desde el estado de funcionamiento o apagado hasta el estado de parada repentina?

1. Si el estado alcanza el nivel de parada, el trabajador deberá ser interrumpido.

2. Es posible que el nivel de parada no se alcance temporalmente, pero si se llama a ShutdownNow en otro lugar inmediatamente, el nivel de parada se alcanzará inmediatamente y debe interrumpirse de inmediato.

Nota: El grupo de subprocesos en sí no responde a las interrupciones y el usuario decide si interrumpir o no en el método task.run.

  • en <5>_

1. <5.1>Es una acción antes de que comience la ejecución de la tarea, está vacía por defecto.

2. <5.2>Es ejecutar la tarea, es decir, llamarrun方法

3. <5.3>Es una acción después de completar la tarea, está vacía por defecto.

  • en <6>_

La variable completeAbruptly indica si la tarea se completó violentamente. Cuando la tarea se ejecuta en <4>, significa que se completó normalmente, no violentamente.

El código se ejecuta en la posición <4>, lo que indica que la tarea recuperada por el método getTask es nula, es decir, no hay suficientes tareas y es necesario destruir los subprocesos innecesarios.

  • en <7>_

En este punto, es necesario finalizar el hilo de trabajo. Hay una explicación detallada más adelante en este párrafo.

2.4 getTask(): Obtener la tarea

private Runnable getTask() {
    
    
    boolean timedOut = false; // Did the last poll() time out?

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

        // <1> 极端条件是:rs == SHUTDOWN,且 workQueue不为空
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
    
    
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // <2> 是否允许取出任务超时
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // <3> 极端条件:wc <= maximumPoolSize 且 wc > corePoolSize 且 超时 且 队列为空,此时需要销毁当前线程
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
    
    
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
    
    
            // <2> 取出任务
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            // <3> 到这里说明当前线程出现了等待超时,则可能要销毁非核心线程
            timedOut = true;
        } catch (InterruptedException retry) {
    
    
            timedOut = false;
        }
    }
}
  • en <1>_

Las condiciones son complicadas en este momento. Veamos la situación extrema. La condición extrema es: [rs == APAGADO y workQueue no está vacío], lo que muestra que incluso en el estado APAGADO, siempre que la cola workQueue no esté vacía , aún debe esperar a que se ejecute workQueue.

SHUTDOWN es un apagado elegante . Esperará a que workQueue termine de ejecutarse.

  • en <2>,

La variable temporizada controla principalmente si es necesario destruir el hilo cuando está inactivo. De forma predeterminada, si el número de subprocesos > corePoolSize y está inactivo, el subproceso actual se destruirá; si el subproceso <= corePoolSize pero está inactivo, el subproceso no se destruirá. (La destrucción predeterminada depende de la cantidad de subprocesos)

  • en <3>_

Condiciones extremas: wc <= maxPoolSize y wc> corePoolSize y se agota el tiempo de espera y la cola está vacía. En este momento, hay demasiados subprocesos y no hay tareas, por lo que el subproceso actual debe destruirse.

  • En <4>, saca la tarea.

1. Si se permite que la tarea de eliminación expire, llame al método de encuesta por tiempo limitado. Devuelve nulo si se agota el tiempo de espera

2. Si no se permite que expire el tiempo de espera de la tarea de recuperación, llame al método take para bloquear la recuperación de la tarea.

Es decir, si se devuelve nulo, debe ser que el subproceso actual está esperando y luego considerar si es necesario destruir subprocesos innecesarios.

2.5 ProcessWorkerExit(): el hilo de trabajo sale

Nota: Aquí es donde realmente sale el subproceso de trabajo . En lugar de llamar al método de apagado del grupo de subprocesos, el subproceso de trabajo se detendrá. El subproceso de trabajo esperará a que se completen todas las tareas antes de detenerse.

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
    /**
     * 1、worker数量-1
     * 如果是突然终止,说明是task执行时异常情况导致,即run()方法执行时发生了异常,
     *那么正在工作的worker线程数量需要-1
     * 如果不是突然终止,说明是worker线程没有task可执行了,不用-1,因为已经在getTask()方法中-1了
     */
    if (completedAbruptly) 
        // If abrupt, then workerCount wasn't adjusted 代码和注释正好相反啊
        decrementWorkerCount();
 
    /**
     * 2、从Workers Set中移除worker
     */
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
    
    
        completedTaskCount += w.completedTasks; //把worker的完成任务数加到线程池的完成任务数
        workers.remove(w); //从HashSet<Worker>中移除
    } finally {
    
    
        mainLock.unlock();
    }
 
    /**
     * 3、在对线程池有负效益的操作时,都需要“尝试终止”线程池
     * 主要是判断线程池是否满足终止的状态
     * 如果状态满足,但还有线程池还有线程,尝试对其发出中断响应,使其能进入退出流程
     * 没有线程了,更新状态为tidying->terminated
     */
    tryTerminate();
 
    /**
     * 4、是否需要增加worker线程
     * 线程池状态是running 或 shutdown
     * 如果当前线程是突然终止的,addWorker()
     * 如果当前线程不是突然终止的,但当前线程数量 < 要维护的线程数量,addWorker()
     * 故如果调用线程池shutdown(),直到workQueue为空前,线程池都会维持corePoolSize个线程,然后再逐渐销毁这corePoolSize个线程
     */
    int c = ctl.get();
    //如果状态是running、shutdown,即tryTerminate()没有成功终止线程池,尝试再添加一个worker
    if (runStateLessThan(c, STOP)) {
    
    
        //不是突然完成的,即没有task任务可以获取而完成的,计算min,并根据当前worker数量判断是否需要addWorker()
        if (!completedAbruptly) {
    
    
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize; //allowCoreThreadTimeOut默认为false,即min默认为corePoolSize
             
            //如果min为0,即不需要维持核心线程数量,且workQueue不为空,至少保持一个线程
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
             
            //如果线程数量大于最少数量,直接返回,否则下面至少要addWorker一个
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
         
        //添加一个没有firstTask的worker
        //只要worker是completedAbruptly突然终止的,或者线程数量小于要维护的数量,就新添一个worker线程,即使是shutdown状态
        addWorker(null, false);
    }
}

ProcessWorkerExit(Trabajador w, booleano completado abruptamente)

trabajador: el trabajador que va a ser despedido

completadoAbruptamente: si se completó repentinamente (si salió debido a una excepción)

Proceso de implementación:

1. Número de trabajadores -1

A、如果是突然终止,说明是task执行时异常情况导致,即run()方法执行时发生了异常,那么正在工作的worker线程数量需要-1

B、如果不是突然终止,说明是worker线程没有task可执行了,不用-1,因为已经在getTask()方法中-1了

2. Elimine al trabajador del conjunto de trabajadores. Mainlock debe estar bloqueado al eliminarlo.
3. tryTerminate(). Lógica aproximada: determinar si el grupo de subprocesos cumple con el estado de terminación

Aunque tryTerminate es muy largo, solo tiene una función: en realidad finaliza el subproceso estableciendo el estado del grupo de subprocesos en terminal. El requisito previo para la terminación es: el estado del grupo de subprocesos es cerrado + la cola está vacía + el subproceso de trabajo es 0

Operación: Actualizar estado a orden->terminado

4. ¿Necesita agregar subprocesos de trabajo? Si el grupo de subprocesos no se ha terminado por completo, aún es necesario mantener una cierta cantidad de subprocesos.

Por ejemplo: se produjo una excepción en nuestro método de tarea personalizada task.run, lo que provocó que el subproceso se cerrara. En este momento, el grupo de subprocesos necesita agregar nuevos subprocesos.

Por lo tanto, si se llama al cierre del grupo de subprocesos (), el grupo de subprocesos mantendrá los subprocesos corePoolSize hasta que workQueue no tenga precedentes y luego destruirá gradualmente los subprocesos corePoolSize.

3.3 Cerrar el grupo de subprocesos

Nota: Cerrar el grupo de subprocesos no detiene los subprocesos en el grupo de subprocesos. Solo le envía una señal de interrupción. Solo envía una señal. ThreadPoolExecute no responde a la señal de interrupción, por lo que el subproceso de trabajo solo finalizará realmente después de que se hayan ejecutado todas las tareas.

3.3.1 Método de apagado

Después del apagado (), el grupo de subprocesos cambiará al estado de apagado. En este momento, no recibirá nuevas tareas **, pero procesará las tareas en ejecución y las tareas en espera de ser procesadas en la cola de bloqueo **.

/**
 * 中断在等待任务的线程(没有上锁的),中断唤醒后,可以判断线程池状态是否变化来决定是否继续
 */
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 {
    
    
                    w.unlock();
                }
            }
            if (onlyOne) break;
        }
    } finally {
    
    
        mainLock.unlock(); //解锁 } }
    }
}

proceso de ejecución de apagado():

1. Bloqueo. MainLock es el bloqueo principal del grupo de subprocesos y es un bloqueo reentrante. Cuando desee operar el conjunto de trabajadores, el HashSet que contiene el subproceso, primero debe obtener el mainLock y cuando desee procesar estadísticas. datos como el tamaño de grupo más grande y el número de tareas completado. Obtenga primero mainLock

2. Utilice la operación CAS para configurar el estado del grupo de subprocesos en apagado. Después del apagado, ya no se recibirán nuevas tareas.

3. Interrumpir todos los subprocesos inactivos interrumpirIdleWorkers()

Los hilos en ejecución no serán interrumpidos

4. onShutdown (), este método se implementa en ScheduledThreadPoolExecutor y puede realizar algún procesamiento durante el apagado ()

5. Desbloquear

6. Intente finalizar el grupo de subprocesos tryTerminate()

Puede ver que los pasos más importantes del método Shutdown() son: actualizar el estado del grupo de subprocesos a apagado , interrumpir todos los subprocesos inactivos y tryTerminate() para intentar finalizar el grupo de subprocesos.

interruptIdleWorkers() interrumpe los subprocesos inactivos de la siguiente manera:

    /**
     * 中断在等待任务的线程(没有上锁的),中断唤醒后,可以判断线程池状态是否变化来决定是否继续
     */
    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 {
    
    
                        w.unlock();
                    }
                }
                if (onlyOne) break;
            }
        } finally {
    
    
            mainLock.unlock(); //解锁 } }
        }
    }

La clave es: si work.tryLock() tiene éxito

Si el trabajador está inactivo (es decir, tryLock tiene éxito), el estado de AQS es de 0 a> 1. Dado que es un subproceso inactivo, el subproceso inactivo se interrumpe.

3.3.2 Método ShutdownNow

Después de ShutdownNow (), el grupo de subprocesos cambiará al estado de detención. En este momento, no aceptará nuevas tareas**, ya no procesará las tareas en espera en la cola de bloqueo y también intentará interrumpir el subproceso de trabajo que se está procesando. **.

En cualquier caso, el grupo de subprocesos ThreadPoolExecute en sí no responde a las interrupciones. En cuanto a si el usuario responde a la interrupción, es asunto del usuario.

/**
 * 尝试停止所有活动的正在执行的任务,停止等待任务的处理,并返回正在等待被执行的任务列表
 * 这个任务列表是从任务队列中排出(删除)的
 * <p>
 * 这个方法不用等到正在执行的任务结束,要等待线程池终止可使用awaitTermination()
 * <p>
 * 除了尽力尝试停止运行中的任务,没有任何保证
 * 取消任务是通过Thread.interrupt()实现的,所以任何响应中断失败的任务可能永远不会结束
 */
public List <Runnable> shutdownNow() {
    
    
    List <Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock(); //上锁
 
    try {
    
    
        //判断调用者是否有权限shutdown线程池
        checkShutdownAccess();
 
        //CAS+循环设置线程池状态为stop
        advanceRunState(STOP);
 
        //中断所有线程,包括正在运行任务的
        interruptWorkers();
 
        tasks = drainQueue(); 
        //将workQueue中的元素放入一个List并返回
    } finally {
    
    
        mainLock.unlock(); 
        //解锁
    }
 
    //尝试终止线程池
    tryTerminate();
 
    return tasks; 
    //返回workQueue中未执行的任务
}

Este método interrumpirá todos los subprocesos, incluidos los subprocesos en ejecución. Si el usuario responde depende del propio usuario .

Las tareas que esperan ser ejecutadas en la cola del grupo de subprocesos se eliminarán y se devolverán al usuario.

Los procesos generales de ShutdownNow() y Shutdown() son similares, las diferencias son:

1. Actualice el grupo de subprocesos para detener el estado.

2. Llame a **interruptWorkers()** para interrumpir todos los subprocesos, incluidos los subprocesos en ejecución.

interruptWorkers primero bloquea mainLock y luego llama al método interruptIfStarted en un bucle para interrumpir todos los subprocesos del trabajador, lo que determinará si el estado AQS del trabajador es mayor que 0, es decir, si el trabajador ha comenzado a operar, y luego llame a t.interrupt().

3. Mueva las tareas que se procesarán en workQueue a una Lista y regrese al final del método, lo que indica que las tareas en workQueue ya no se procesarán después de ShutdownNow ().

4. Otros métodos

4.1 método de rechazo

private static final RejectedExecutionHandler defaultHandler =
        new AbortPolicy();

final void reject(Runnable command) {
    
    
    handler.rejectedExecution(command, this);
}

public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
    throw new RejectedExecutionException("Task " + r.toString() +
                                         " rejected from " +
                                         e.toString());
}

El controlador predeterminado es AbortPolicy, su método de manejo es "lanzar una excepción".

Otros manejadores:

  • Descartar Política

Descartar directamente

  • Descartar política más antigua

Descartar los "más antiguos"

  • Política de ejecuciones de llamadas

hilo de llamada para ejecutar . Pero si se cierra el grupo de subprocesos, la tarea se descartará.

5. Resumen

1. Tome los 3 bits superiores de la variable ctl de tipo int para indicar el estado del grupo de subprocesos y los 29 bits inferiores para indicar la cantidad de subprocesos en el grupo de subprocesos.

2. Ser capaz de comprender el proceso general de las tareas de ejecución del grupo de subprocesos (primero el núcleo, luego la cola, luego el máximo y luego rechazar)

3. Puede entender por qué hay una clase de Trabajo (principalmente para interrupción)

4. ¿Qué condiciones debe cumplir el hilo de trabajo antes de salir?

La tarea se cerrará si se produce una excepción, pero se agregarán nuevos subprocesos.

Solo cuando se ejecuten todas las tareas y se llame al método de apagado, el sistema realmente saldrá.

5. El impacto del método de apagado en el hilo de trabajo.

el apagado interrumpirá los subprocesos inactivos

El apagado cambiará el estado del grupo de subprocesos a apagado y el subproceso de trabajo reaccionará al estado.

  • No se pueden agregar nuevas tareas después del cierre (reflejado en addWorker)

  • El hilo de trabajo sale después de ejecutar la tarea en lugar de esperar nuevas tareas en la cola workQueue.

6. Para revisar el pasado y aprender lo nuevo, debes mirarlo con frecuencia y pensar en ello con frecuencia.

6. Referencia

https://blog.csdn.net/qq_41573860/article/details/123291943

Supongo que te gusta

Origin blog.csdn.net/yuchangyuan5237/article/details/133455708
Recomendado
Clasificación