Interpretación y principio del código fuente del grupo de subprocesos

prefacio

Programador mayor Lao Wang Lao
Wang es un programador que ha estado a la deriva en Beijing durante más de diez años. Es demasiado mayor para trabajar horas extras pero no puede ser ascendido. Eligió la industria del baño y abrió un centro de baño, sí, un centro de baño regular. Cuando estuvo en Beijing antes, la casa de baños a la que le gustaba ir se llamaba "Piscina Tsinghua". Después de pensarlo, llamó a su centro de baños "Piscina Thread".

Centro de baño Thread Pool
Después de que Thread Pool abrió, Lao Wang descubrió que algunos clientes querían hacerse pedicuras, por lo que contrató a un técnico de pedicura y agregó un negocio adicional para aumentar los ingresos. A medida que aumentaba el número de clientes de pedicura, se contrataron cuatro técnicos de pedicura más para ganar más dinero.
Después de un período de tiempo, el negocio del centro de baño mejora cada vez más, y cada vez hay más clientes que hacen pedicura. Sin embargo, Lao Wang descubrió que ya había 5 técnicos de pedicura en su tienda, que habría demasiados reclutas y que no podía permitirse pagar más salarios. ¿Qué sucede si el técnico de pedicura está demasiado ocupado? Lao Wang es un hombre inteligente, por lo que inmediatamente pensó en una solución: dejar que los clientes hagan fila, y si algún técnico de pedicura ha terminado y está libre, le pedirá a otro cliente en la fila que continúe con el trabajo.

Fines de semana ocupados
Los fines de semana, el número de clientes que acuden al centro de baño es varias veces superior al habitual, el tiempo de cola para los clientes que quieren pedicura es demasiado largo y los clientes ya están impacientes. Lao Wang reaccionó de inmediato y reclutó urgentemente a 5 técnicos de pedicura de otros centros de baño para que hicieran pedicuras para los clientes del equipo, lo que redujo en gran medida la cantidad de clientes en fila.
Sin embargo, a veces el negocio está tan caliente que se utilizan técnicos reclutados con urgencia, y el tiempo de cola para los clientes también es muy largo. Cuando llegan nuevos clientes, Lao Wang solo puede decirle al cliente con una sonrisa en su rostro: "Vuelve a continuación". vez, la próxima vez Encuentre un buen técnico para usted", excluyendo a los clientes.
Después del fin de semana, la tienda no pudo soportar a los ociosos, por lo que Lao Wang despidió a todos los técnicos reclutados con urgencia.

El método de gestión de Lao Wang
El negocio de Lao Wang está en auge y pronto abrirá una sucursal, recaudará fondos para cotizar en bolsa y alcanzará la cima de su vida. Ya que tiene tanto éxito, repasemos sus métodos comerciales:
————————————————

1. Introducción al grupo de subprocesos

1.1 Concepto básico de hilo

El ciclo de vida del hilo es el siguiente:

inserte la descripción de la imagen aquí

新建:java.lang.Thread.State.NEW
public static void thread_state_NEW(){
    
    
        Thread thread = new Thread();
        System.out.println(thread.getState());
    }
就绪:java.lang.Thread.State.RUNNABLE
public static void thread_state_RUNNABLE(){
    
    
        Thread thread = new Thread();
        thread.start();
        System.out.println(thread.getState());
    }
超时等待:java.lang.Thread.State#TIMED_WAITING
public static void thread_state_SLEEP(){
    
    
        Thread thread3 = new Thread(() -> {
    
    
            try {
    
    
                Thread.sleep(10000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } });
        thread3.start();
        Thread.sleep(500);
        System.out.println(thread3.getState());
    }
等待:java.lang.Thread.State.WAITING
public static void thread_state_WAITING(){
    
    
        Thread thread2 = new Thread(new Runnable() {
    
    
            public void run() {
    
    
                LockSupport.park();
            }
        });
        thread2.start();
        Thread.sleep(500);
        System.out.println(thread2.getState());
        LockSupport.unpark(thread2);
    }
阻塞:java.lang.Thread.State.BLOCKED
    public static void thread_state_BLOCKED(){
    
    
        final byte[] lock = new byte[0];
        Thread thread1 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
                try {
    
    
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } }
        });
        thread1.start();
        Thread thread2 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
            } });
        thread2.start();
        Thread.sleep(1000);
 
        System.out.println(thread1.getState());
        System.out.println(thread2.getState());
    }
销亡:java.lang.Thread.State.TERMINATED

public static void thread_state_TERMINATED(){
    
    
        Thread thread = new Thread();
        thread.start();
        Thread.sleep(1000);
        System.out.println(thread.getState());
    }

1.2 Concepto básico del grupo de subprocesos

1.2.1 ¿Por qué usar el grupo de subprocesos?

También hay algunas precauciones para usar el grupo de subprocesos en el proyecto. Consulte el "Manual de desarrollo de Java - Edición Taishan" para obtener instrucciones:
[Obligatorio] Los recursos de subprocesos deben proporcionarse a través del grupo de subprocesos y no está permitido crear subprocesos explícitamente En la aplicacion.
Descripción: la ventaja del grupo de subprocesos es reducir el tiempo dedicado a crear y destruir subprocesos y la sobrecarga de los recursos del sistema, y ​​resolver el problema de recursos insuficientes. Si no se utiliza el grupo de subprocesos, puede hacer que el sistema cree una gran cantidad de subprocesos del mismo tipo, lo que genera un consumo de memoria o un "cambio excesivo".
[Obligatorio] No está permitido usar Ejecutores para crear grupos de subprocesos, pero sí usar ThreadPoolExecutor. Este método de procesamiento permite a los estudiantes tener más claridad sobre las reglas de ejecución de los grupos de subprocesos y evitar el riesgo de agotamiento de recursos.

Las desventajas de los objetos de grupo de subprocesos devueltos por los ejecutores son las siguientes:
FixedThreadPool y SingleThreadPool:
la longitud de la cola de solicitudes permitida es Integer.MAX_VALUE, que puede acumular una gran cantidad de solicitudes, lo que resulta en OOM.
CachedThreadPool:
la cantidad de subprocesos que se pueden crear es Integer.MAX_VALUE, lo que puede crear una gran cantidad de subprocesos, lo que resulta en OOM.

1.2.2 Principio

Grupo de subprocesos (ThreadPool): El grupo de subprocesos es para crear un grupo de búfer para almacenar subprocesos. Después de la ejecución, el subproceso no morirá, sino que volverá al grupo de subprocesos nuevamente para quedar inactivo, esperando que llegue la siguiente tarea, que hace que el grupo de subprocesos sea más que manual La creación de subprocesos tiene más ventajas y
se usa a menudo en escenarios de alta simultaneidad. El uso de subprocesos múltiples para optimizar la eficiencia del código, por lo tanto, el uso de un grupo de subprocesos tiene más ventajas que la creación manual de subprocesos:
reduce el consumo de recursos del sistema y reduce el consumo causado por la creación y destrucción de subprocesos mediante la reutilización de subprocesos existentes;

Mejorar la velocidad de respuesta del sistema, cuando llega una tarea, al reutilizar el hilo existente, se puede ejecutar inmediatamente sin esperar a la creación de un nuevo hilo, es conveniente para el control del número de hilos concurrentes
. Porque si el subproceso se crea sin límite, puede causar un uso excesivo de la memoria y OOM para
ahorrar el costo de tiempo de los subprocesos de cambio de CPU (es necesario mantener el sitio del subproceso de ejecución actual y restaurar el sitio del subproceso de ejecución)
para proporcionar funciones más potentes y retrasar el grupo de subprocesos de sincronización. Timer vs ScheduledThreadPoolExecutor
estructura de grupo de subprocesos comunes (UML)

inserte la descripción de la imagen aquí

Executor
: la interfaz de nivel superior Executor ofrece una idea: desacoplar el envío de tareas y la ejecución de tareas.
ExecutorService
amplía la capacidad de ejecutar tareas, complementa el método que puede generar Future para una o un lote de tareas asincrónicas y
proporciona métodos para administrar y controlar el grupo de subprocesos, como detener la operación del grupo de subprocesos.
La clase abstracta de la capa superior de AbstractExecutorService conecta el proceso de ejecución de tareas en serie para garantizar que la implementación de la capa inferior solo necesite centrarse en un método de ejecución de tareas. ThreadPoolExecutor es el grupo de subprocesos
más utilizado. Por un lado . , mantiene su propio ciclo de vida, y por otro lado, maneja hilos al mismo tiempo y tareas, de manera que los dos están bien combinados para realizar tareas paralelas

1.2.3 Estado del grupo de subprocesos

inserte la descripción de la imagen aquí

EN EJECUCIÓN: acepte nuevas tareas y procese tareas en cola.
CIERRE: No acepte nuevas tareas, pero procese las tareas en cola.
DETENER: no acepte nuevas tareas, no procese tareas en cola e interrumpa las tareas en curso.
ORDENANDO: Todas las tareas han sido terminadas, el número de trabajadores es cero, y el subproceso en transición al estado ORDENANDO ejecutará el método de enganche terminado().
TERMINADO: terminado () completado.
Los anteriores son los cinco estados del grupo de subprocesos, entonces, ¿cuáles son estos cinco estados registrados? Márcalo ~ los siguientes detalles.

1.2.4 Proceso de ejecución

inserte la descripción de la imagen aquí

Escenario hipotético:
cree un grupo de subprocesos, agregue tareas en un bucle infinito y depure para ver el patrón de crecimiento de la cantidad de trabajos y colas.
Después de esperar un período de tiempo, verifique si la cantidad de trabajos vuelve al núcleo

Primero adjunte la conclusión:
agregue una tarea, si la cantidad de subprocesos en el grupo de subprocesos no alcanza el tamaño del núcleo, cree un nuevo subproceso directamente para ejecutar. Cuando se
alcance el núcleo, póngalo en la cola. La
cola está llena. Si no se alcanza el maxSize, continúe creando subprocesos
.
Cuando se alcance el maxSize, el subproceso se rechazará de acuerdo con la política de rechazo. release, down to coreSize

2. Principio de funcionamiento

Introducción de parámetros
Primero, entendamos el constructor de ThreadPoolExecutor
Desde el código fuente, podemos ver que el constructor de ThreadPoolExecutor tiene 7 parámetros, a saber, corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory y handler. A continuación se explicarán estos 7 parámetros uno por uno.

inserte la descripción de la imagen aquí

2.1 corePoolSize número de subprocesos principales:

Se mantendrá un número mínimo de subprocesos en el grupo de subprocesos. Incluso si estos subprocesos están inactivos, no se destruirán a menos que se establezca allowCoreThreadTimeOut. El número mínimo de subprocesos aquí es corePoolSize. Después de enviar la tarea al grupo de subprocesos, primero verificará si la cantidad actual de subprocesos ha alcanzado el corePoolSize; de ​​lo contrario, se creará un nuevo subproceso para procesar la tarea.

2.2 maximumPoolSize El número máximo de subprocesos en el grupo de subprocesos:

Después de que la cantidad actual de subprocesos alcance corePoolSize, si las tareas continúan enviándose al grupo de subprocesos, las tareas se almacenarán en caché en la cola de trabajo (descrito más adelante). Si la cola también está llena, se creará un nuevo hilo para manejar esto. El grupo de subprocesos no creará nuevos subprocesos de forma indefinida, tendrá un límite en la cantidad máxima de subprocesos, que se especifica en maximumPoolSize.

2.3 tiempo de supervivencia del subproceso inactivo de keepAliveTime:

  一个线程如果处于空闲状态,并且当前的线程数量大于corePoolSize,那么在指定时间后,这个空闲线程会被销毁,这里的指定时间由keepAliveTime来设定

Unidad de tiempo de supervivencia de subproceso inactivo de 2,4 unidades:

La unidad de medida de keepAliveTime, comúnmente utilizada SEGUNDOS (segundos) MILISEGUNDOS (milisegundos)

2.5 cola de trabajo cola de trabajo:

Cola de tareas, una cola de bloqueo para transferir y guardar tareas en espera de ser ejecutadas. Cuando se complete la inicialización de corePoolSize, la siguiente tarea se almacenará directamente en la cola y el subproceso girará para obtener la tarea a través del método getTask(). Las configuraciones de cola comunes son las siguientes:

①ArrayBlockingQueue Cola de bloqueo de matriz:
Cola de bloqueo delimitada basada en matriz, ordenada por FIFO. Cuando ingresan nuevas tareas, se colocarán al final de la cola y una matriz delimitada puede evitar el agotamiento de los recursos. Cuando la cantidad de subprocesos en el grupo de subprocesos alcanza corePoolSize y entra una nueva tarea, la tarea se colocará al final de la cola, esperando ser programada. Si la cola ya está llena, se crea un nuevo hilo, y si el número de hilos ha alcanzado maxPoolSize, se ejecutará la estrategia de rechazo.

②※LinkedBlockingQuene cola de bloqueo de lista enlazada (nota: se puede especificar la longitud): una
cola de bloqueo ilimitada basada en la lista enlazada (la capacidad máxima predeterminada es Interger.MAX y la longitud se puede especificar), ordenada según FIFO. Debido a la naturaleza aproximadamente ilimitada de la cola, cuando la cantidad de subprocesos en el grupo de subprocesos alcanza corePoolSize, las tareas nuevas siempre se almacenarán en la cola y, básicamente, no crearán nuevos subprocesos hasta maxPoolSize (es difícil alcanzar la cantidad de subprocesos). Interger.MAX), por lo que al usar esta cola de trabajo, el parámetro maxPoolSize en realidad no tiene ningún efecto.

③SynchronousQuene Synchronous Queue:
una cola de bloqueo que no almacena tareas en caché. El productor pone una tarea y debe esperar hasta que el consumidor la saque. Es decir, cuando entra una nueva tarea, no se almacenará en caché, sino que se programará directamente para ejecutar la tarea. Si no hay un hilo disponible, se creará un nuevo hilo. Si el número de hilos alcanza maxPoolSize, se ejecutará la estrategia de rechazo.

④PriorityBlockingQueue Cola de bloqueo de prioridad:
una cola de bloqueo ilimitada con prioridad, la prioridad se realiza mediante el parámetro Comparator.

2.6, fábrica de hilos threadFactory:

La fábrica utilizada al crear un nuevo subproceso se puede utilizar para establecer el nombre del subproceso, ya sea un subproceso demonio, etc.

2.7, estrategia de rechazo del controlador:

Cuando las tareas en la cola de trabajo han alcanzado el límite máximo y la cantidad de subprocesos en el grupo de subprocesos también ha alcanzado el límite máximo, si se envía una nueva tarea, cómo tratarla. La estrategia de rechazo aquí es para resolver este problema.jdk proporciona 4 estrategias de rechazo:
①CallerRunsPolicy
Bajo esta estrategia, el método de ejecución de la tarea rechazada se ejecuta directamente en el subproceso de la persona que llama.
②AbortPolicy
Según esta política, la tarea se descarta directamente y se genera una excepción de ejecución rechazada.
pd: ThreadPoolTaskExecutor tiene como valor predeterminado
③DiscardPolicy
según esta política, descarta directamente la tarea y no hace nada.
④DiscardOldestPolicy
Bajo esta política, descartar la primera tarea que ingresó a la cola y luego intentar colocar la tarea rechazada en la cola

3 Análisis del código fuente

Después de introducir la información básica del conjunto de subprocesos anterior, el análisis del código fuente comenzará a continuación. Primero observe el concepto básico del código fuente.

3.1 Concepto básico: CTL

¿Qué es "ctl"?
ctl es un entero atómico que contiene dos campos conceptuales.
1) workerCount: indica el número efectivo de subprocesos;
2) runState: indica el estado de ejecución del grupo de subprocesos, incluidos RUNNING, SHUTDOWN, STOP, ORDENANDO, TERMINATED y otros estados.

El tipo int tiene 32 bits, de los cuales los 29 bits inferiores de ctl se utilizan para representar el número de trabajadores y los 3 bits superiores se utilizan para representar el estado de ejecución, como se muestra en la siguiente figura.

inserte la descripción de la imagen aquí

Introducción al código fuente:

/**
     * 主池控制状态ctl是包含两个概念字段的原子整数: workerCount:指有效的线程数量;
     * runState:指运行状态,运行,关闭等。为了将workerCount和runState用1个int来表示,
     * 我们限制workerCount范围为(2 ^ 29) - 1,即用int的低29位用来表示workerCount,
     * 用int的高3位用来表示runState,这样workerCount和runState刚好用int可以完整表示。
     */
    // 初始化时有效的线程数为0, 此时ctl为: 1010 0000 0000 0000 0000 0000 0000 0000
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    // 高3位用来表示运行状态,此值用于运行状态向左移动的位数,即29位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    // 线程数容量,低29位表示有效的线程数, 0001 1111 1111 1111 1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 
    /**
     * 大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED,
     * 源码中频繁使用大小关系来作为条件判断。
     * 1110 0000 0000 0000 0000 0000 0000 0000 运行
     * 0000 0000 0000 0000 0000 0000 0000 0000 关闭
     * 0010 0000 0000 0000 0000 0000 0000 0000 停止
     * 0100 0000 0000 0000 0000 0000 0000 0000 整理
     * 0110 0000 0000 0000 0000 0000 0000 0000 终止
     */
    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; // 终止

estado de ejecución obtener:

/**
 * 得到运行状态:入参c为ctl的值,~CAPACITY高3位为1低29位全为0,
 * 因此运算结果为ctl的高3位, 也就是运行状态
 */
private static int runStateOf(int c)     {
    
     return c & ~CAPACITY; }

workCount obtener:

/**
     * 得到有效的线程数:入参c为ctl的值, CAPACITY高3为为0,
     * 低29位全为1, 因此运算结果为ctl的低29位, 也就是有效的线程数
     */
    private static int workerCountOf(int c)  {
    
     return c & CAPACITY; }

3.2 ¿Cuáles son las ventajas de tal diseño de CTL?

La principal ventaja del diseño de ctl es que encapsula las operaciones en runState y workerCount en una operación atómica.
runState y workerCount son los dos atributos más importantes en el funcionamiento normal del grupo de subprocesos, lo que debe hacer el grupo de subprocesos en un momento determinado depende de los valores de estos dos atributos.

Por lo tanto, ya sea consulta o modificación, debemos asegurarnos de que las operaciones sobre estos dos atributos pertenecen al "mismo momento", es decir, operaciones atómicas, de lo contrario se producirá una confusión. Si usamos dos variables para almacenar por separado, se requieren operaciones de bloqueo adicionales para garantizar la atomicidad, lo que obviamente generará una sobrecarga adicional, pero encapsular estas dos variables en un AtomicInteger no generará una sobrecarga adicional. obtenido por separado mediante operaciones simples con bits.

3.3 Escenario de depuración de código fuente

Sigue siendo el escenario hipotético anterior:
crear un grupo de subprocesos, agregar tareas en un bucle infinito, depurar para ver la ley de crecimiento del número de trabajos y colas,
subproceso central 3, cola limitada 2, subproceso máximo 5
tiempo de espera 20 s, estrategia de rechazo personalizada
Después de esperar un período de tiempo, verifique la cantidad de trabajos. Si volver al
escenario de la tarea principal
. Se abrió el grupo de subprocesos. En los primeros tres días, hubo grandes recompensas. La velocidad de los visitantes fue mayor que la velocidad de consumo. La observación aumentó gradualmente. El
grupo de subprocesos comenzó el octavo día, la cantidad de tareas disminuyó y la velocidad de consumo fue mayor que la velocidad de producción. Observando una tendencia decreciente gradual

/**
     * desc : 回落场景
     */
    @SneakyThrows
    private static void test_threadPoolExecutor_down_core() {
    
    
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                5,
                20,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                new RejectedExecutionHandler() {
    
    
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    
    
                        System.out.println("爆满了,择日再来啊!!!!!拒绝任务~~~~~~~~~~~~~~~");
                    }
                });
 
        // 开业前七天高峰,瞬间打满
        for (int i = 0; i < 100; i++) {
    
    
 
            int finalI = i + 1;
            executor.execute(() -> {
    
    
                try {
    
    
                    System.out.println("~~~~~~~~~~~~~~~~~~~~~~来活了,第" + finalI + "位客人~~~~~~~~~~~~~~~~~~~~~~");
                    System.out.println("当前排队任务数: " + executor.getQueue().size() + "  当前线程数量: " + 
                    executor.getPoolSize());
                    Thread.sleep(10 * 1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            });
            if (i >= 7) {
    
    
                if (i == 8) {
    
    
                    System.out.println("线程任务高峰期已过 | 分界线!!!!!!!!!!");
                }
                // 此时任务产生的速率高于线程执行速度(线程有富余)
                Thread.sleep(15 * 1000);
            } else {
    
    
                Thread.sleep(1L * 1000);
            }
        }
    }

Aquí está el resultado:
los subprocesos aumentan gradualmente a 3, luego la cola aumenta a 2 y luego los subprocesos aumentan a 5. Cuando llegue la séptima misión, asciende a la cima.
inserte la descripción de la imagen aquí

Las tareas subsiguientes se generan más lentamente, las tareas en cola se reducen en 2->0 y, luego, la cantidad de subprocesos se reduce gradualmente en 5->3

inserte la descripción de la imagen aquí

A través de los casos anteriores, puede observar el proceso de cambios dinámicos en el grupo de subprocesos.La razón de este fenómeno se analizará desde la perspectiva del código fuente. [ps: siga el código fuente desde una perspectiva amplia a una profunda]

3.4 Proceso de depuración del código fuente

public void execute(Runnable command) {
    
    
        // 防御性容错
        if (command == null)
            throw new NullPointerException();
        int c = ctl.get();
        // case1 -> worker数量小于核心线程数,addWork
        if (workerCountOf(c) < corePoolSize) {
    
    
            // 添加worker - core
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        // case2 -> 如果线程池还在运行态,offer到队列
        if (isRunning(c) && workQueue.offer(command)) {
    
    
            //再检查一下状态
            int recheck = ctl.get();
            //如果线程池已经终止,直接移除任务,不再响应
            if (! isRunning(recheck) && remove(command))
                reject(command);
            //否则,如果没有线程干活的话,创建一个空work,该work会从队列获取任务去执行
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        // case3 -> 队列也满,继续调addWork,但是注意,core=false,开启到maxSize的大门
        else if (!addWorker(command, false)) {
    
    
            // case4 -> 超出max的话,addWork会返回false,进入reject
            reject(command);
        }
    }

Luego ingrese el método addWork, que proporciona dos parámetros de entrada (tarea, subproceso principal o no), la lógica interna es la siguiente:

/**
     * desc : 线程创建过程
     */
    private boolean addWorker(Runnable firstTask, boolean core) {
    
    
        // 第一步,先是ctl-wc 通过CAS + 1
        retry:
        for (;;) {
    
    
            int c = ctl.get();
            int rs = runStateOf(c);
 
            // 判断线程池状态是否是可运行态(停止及之后 直接falsee)
            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;
                // 满足条件,此时ctl-wc CAS原子性增加,正常break
                if (compareAndIncrementWorkerCount(c))
                    break retry;
                // 增加失败,判断线程池状态决定内循环 or 外循环(结束)
                c = ctl.get();
                if (runStateOf(c) != rs)
                    continue retry;
            }
        }
 
        // 第二步,创建新work放入线程集合works(一个HashSet)
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
    
    
            // 符合条件,创建新的work并包装task
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
    
    
                // 加锁 - 避免workers的线程安全问题
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
    
    
                    // 再次校验运行状态,防止关闭
                    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 {
    
    
            // 添加失败,减ctl,集合内移除
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

Aquí declaramos enfáticamente nuestro objetivo trabajador, Trabajador

private final class Worker extends AbstractQueuedSynchronizer
     implements Runnable{
    
    
         /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
}

Worker es un subproceso de trabajo que implementa la interfaz Runnable y contiene un subproceso de subproceso y una tarea inicializada firstTask. thread es un hilo creado por ThreadFactory cuando se llama al constructor, y se puede usar para ejecutar tareas; firstTask lo usa para guardar la primera tarea pasada, que puede ser nula o nula. Si este valor no es nulo, el subproceso ejecutará esta tarea inmediatamente al comienzo del inicio, lo que corresponde a la situación cuando se crea el subproceso principal; si este valor es nulo, entonces se debe crear un subproceso para ejecutar el tareas en la lista de tareas (workQueue) Tareas, es decir, la creación de subprocesos no centrales.

El trabajador hereda AQS y usa AQS para realizar la función de bloqueo exclusivo. ReentrantLock no se usa, pero AQS se usa para realizar la función de no reentrada para reflejar el estado de ejecución actual del subproceso. Se utiliza para reciclar hilos.
inserte la descripción de la imagen aquí

Continuemos siguiendo la clase Worker y veamos su método de ejecución.

//在worker执行runWorker()的时候,不停循环,先查看自己有没有携带Task,如果有,执行
   while (task != null || (task = getTask()) != null)
 
 
//如果没有绑定,会调用getTask,从队列获取任务
    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);
 
            // 判断是不是要超时处理,重点!!!决定了当前线程要不要被释放
            // 首次进来 allowCoreThreadTimeOut = false 主要看 wc > corePoolSize
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            //线程数超出max,并且上次循环中poll等待超时了,那么说明该线程已终止 //将线程队列数量原子性减
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
    
    
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }
            try {
    
    
                // 重点!!!
                // 如果线程可被释放,那就poll,释放的时间为:keepAliveTime
                // 否则,线程是不会被释放的,take一直被阻塞在这里,直到来了新任务继续工作
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                //到这里说明可被释放的线程等待超时,已经销毁,设置该标记,下次循环将线程数减少
                timedOut = true;
            } catch (InterruptedException retry) {
    
    
                timedOut = false;
            }
        }
    }

Finalmente, la salida de nuestro Worker (la liberación del hilo)

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
    
    
 
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
    
    
            // 统计总执行任务数 && 释放worker
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
    
    
            mainLock.unlock();
        }
        // 中断线程
        tryTerminate();
}

Revisión completa del proceso:
inserte la descripción de la imagen aquí

3.5 Notas

¿Cómo asegura el grupo de subprocesos que los subprocesos centrales no se destruyan?¿
Cómo mueren los subprocesos no centrales después de keepAliveTime?
Cuando la cantidad de subprocesos principales es menor que corePoolSize y hay subprocesos inactivos, agregar tareas en este momento es crear subprocesos o ejecución existente. ¿Cuál es
la diferencia entre subprocesos principales y subprocesos no centrales? ¿
Qué bloqueos se usan en el grupo de subprocesos y ¿por qué?

Supongo que te gusta

Origin blog.csdn.net/chuige2013/article/details/131119377
Recomendado
Clasificación