Análisis del código fuente del principio subyacente ThreadPoolExecutor del grupo de subprocesos

¿Cuál es el proceso específico de las tareas de ejecución del grupo de subprocesos?

ThreadPoolExecutor proporciona dos métodos para ejecutar tareas:

  • ejecución nula (comando ejecutable)
  • Futuro<?> enviar (tarea ejecutable)

De hecho, el método ejecutar () finalmente se llama al enviar, pero devuelve un objeto Futuro para obtener el resultado de la ejecución de la tarea:

public Future<?> submit(Runnable task) {
    
    
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}

El método de ejecución (comando ejecutable) se ejecutará en tres pasos:
Insertar descripción de la imagen aquí

Aviso:

  • Al enviar un Runnable, independientemente de si los subprocesos en el grupo de subprocesos actual están inactivos o no, siempre que el número sea menor que el número de subprocesos principales, se crearán nuevos subprocesos.
  • ThreadPoolExecutor es equivalente a injusticia. Por ejemplo, un Runnable enviado después de que la cola esté llena puede ejecutarse antes de que el Runnable se ponga en cola.

¿Cómo fluyen los cinco estados del grupo de subprocesos?

El grupo de subprocesos tiene cinco estados.

  • EN EJECUCIÓN: Se recibirán nuevas tareas y se procesarán las tareas en la cola.
  • APAGADO: No se aceptarán nuevas tareas y se procesarán las tareas en la cola
  • DETENER: No se recibirán nuevas tareas, las tareas en la cola no se procesarán y las tareas que se estén procesando se interrumpirán (nota: si una tarea se puede interrumpir depende de la tarea en sí)
  • ORDENANDO: Todas las tareas han finalizado y no hay subprocesos en el grupo de subprocesos, por lo que el estado del grupo de subprocesos cambiará a ORDENADO. Una vez que se alcance este estado, se llamará a terminado () del grupo de subprocesos.
  • TERMINADO: Después de ejecutar terminado(), cambiará a TERMINADO

Estos cinco estados no se pueden convertir arbitrariamente y solo existirán las siguientes situaciones de conversión:

  • EN EJECUCIÓN -> APAGADO: Se activa llamando manualmente a apagado(), o se llamará a finalize() durante el GC del objeto del grupo de subprocesos para llamar a apagado()
  • (EN EJECUCIÓN o APAGADO) -> DETENER: Se activa llamando a ShutdownNow (). Si se llama primero a Shutdown () y se llama a ShutdownNow () inmediatamente, se producirá APAGADO -> DETENER.
  • APAGADO -> ORDENAR: conversión automática cuando la cola está vacía y no hay subprocesos en el grupo de subprocesos
  • DETENER -> ORDENAR: cambia automáticamente cuando no hay subprocesos en el grupo de subprocesos (puede haber tareas en la cola)
  • ORDENAR -> TERMINADO: Se convertirá automáticamente después de ejecutar terminado ().

Cómo se cierran los subprocesos en el grupo de subprocesos

  • Generalmente usamos el método thread.start() para iniciar un hilo, pero ¿cómo detener un hilo?
  • La clase Thread proporciona un stop (), pero está marcado como @Deprecated ¿Por qué no se recomienda utilizar el método stop () para detener el hilo?
  • Debido a que el método stop() es demasiado crudo, una vez que se llama a stop(), el hilo se detendrá directamente. Sin embargo, al llamar, no tienes idea de qué estaba haciendo el hilo o qué paso ha alcanzado la tarea. Esto es muy peligroso.
  • Aquí se enfatiza que stop() liberará el bloqueo sincronizado ocupado por el subproceso (el bloqueo ReentrantLock no se liberará automáticamente, lo cual también es un factor por el cual no se recomienda stop()) .
public class ThreadTest {
    
    
    static int count = 0;
    static final Object lock = new Object();
    static final ReentrantLock reentrantLock = new ReentrantLock();

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

        Thread thread = new Thread(new Runnable() {
    
    
            public void run() {
    
    
//                synchronized (lock) {
    
    
                reentrantLock.lock();
                for (int i = 0; i < 100; i++) {
    
    
                    count++;
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
//                }
                reentrantLock.unlock();
            }
        });

        thread.start();
        Thread.sleep(5*1000);
        thread.stop();
//
//        Thread.sleep(5*1000);

        reentrantLock.lock();
        System.out.println(count);
        reentrantLock.unlock();

//        synchronized (lock) {
    
    
//            System.out.println(count);
//        }
    }
}

Por lo tanto, recomendamos detener un hilo personalizando una variable o interrumpiéndolo, como por ejemplo:

public class ThreadTest {
    
    

    static int count = 0;
    static boolean stop = false;

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

        Thread thread = new Thread(new Runnable() {
    
    
            public void run() {
    
    

                for (int i = 0; i < 100; i++) {
    
    
                    if (stop) {
    
    
                        break;
                    }

                    count++;
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        });

        thread.start();
        Thread.sleep(5 * 1000);
        stop = true;
        Thread.sleep(5 * 1000);
        System.out.println(count);
    }
}

La diferencia es que cuando configuramos stop en verdadero, el hilo mismo puede controlar si se detiene y cuándo. De manera similar, podemos llamar a la interrupción () del hilo para interrumpir el hilo:

public class ThreadTest {
    
    

    static int count = 0;
    static boolean stop = false;

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

        Thread thread = new Thread(new Runnable() {
    
    
            public void run() {
    
    

                for (int i = 0; i < 100; i++) {
    
    
                    if (Thread.currentThread().isInterrupted()) {
    
    
                        break;
                    }

                    count++;
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        break;
                    }
                }
            }
        });

        thread.start();
        Thread.sleep(5 * 1000);
        thread.interrupt();
        Thread.sleep(5 * 1000);
        System.out.println(count);
    }
}

La diferencia es que si el hilo se interrumpe durante el sueño, se recibirá una excepción.
De hecho, la interrupción () se usa para detener subprocesos en el grupo de subprocesos . Por ejemplo, el método ShutdownNow () llamará

void interruptIfStarted() {
    
    
    Thread t;
    if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
        try {
    
    
            t.interrupt();
        } catch (SecurityException ignore) {
    
    
        }
    }
}

¿Por qué el grupo de subprocesos tiene que ser una cola de bloqueo?

  Durante el proceso de ejecución, los subprocesos en el grupo de subprocesos continuarán obteniendo tareas de la cola y ejecutándolas después de ejecutar la primera tarea vinculada al crear el subproceso, luego, si no hay tareas en la cola, el subproceso no morirá naturalmente. Se bloqueará al adquirir la tarea de la cola, y cuando haya una tarea en la cola, la obtendrá y la ejecutará.
  En última instancia, este método puede garantizar que se pueda reservar un número específico de subprocesos centrales en el grupo de subprocesos. El código clave es:

try {
    
    
    Runnable r = timed ?
        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
        workQueue.take();
    if (r != null)
        return r;
    timedOut = true;
} catch (InterruptedException retry) {
    
    
    timedOut = false;
}

  Cuando un subproceso obtiene una tarea de la cola, determinará si debe usar el bloqueo de tiempo de espera para obtenerla. Podemos pensar que el subproceso no central sondeará (), el subproceso central tomará () y el subproceso no central realizará una encuesta (). Naturalmente, no podrá obtener la tarea después de que expire el tiempo.

Si ocurre una excepción en un subproceso, ¿se eliminará del grupo de subprocesos?

La respuesta es sí, entonces, ¿es posible que la cantidad de subprocesos principales sea incorrecta al ejecutar tareas, lo que provoca que todos los subprocesos principales se eliminen del grupo de subprocesos?
Insertar descripción de la imagen aquí

  • En el código fuente, cuando ocurre una excepción al ejecutar una tarea, eventualmente se ejecutará ProcessWorkerExit (). Después de ejecutar este método, el hilo actual morirá naturalmente.
  • Sin embargo, se agregará un subproceso adicional en el método ProcessWorkerExit(), para que se pueda mantener el número fijo de subprocesos principales.

Cómo personaliza Tomcat el grupo de subprocesos

El grupo de subprocesos utilizado en Tomcat es org.apache.tomcat.util.threads.ThreadPoolExecutor. Tenga en cuenta que el nombre de la clase es el mismo que el de JUC, pero el nombre del paquete es diferente.
Tomcat creará este grupo de subprocesos:

public void createExecutor() {
    
    
    internalExecutor = true;
    TaskQueue taskqueue = new TaskQueue();
    TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
    executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
    taskqueue.setParent( (ThreadPoolExecutor) executor);
}

La cola entrante es TaskQueue y su lógica de puesta en cola es:

public boolean offer(Runnable o) {
    
    
    //we can't do any checks
    if (parent==null) {
    
    
        return super.offer(o);
    }

    //we are maxed out on threads, simply queue the object
    if (parent.getPoolSize() == parent.getMaximumPoolSize()) {
    
    
        return super.offer(o);
    }

    //we have idle threads, just add it to the queue
    if (parent.getSubmittedCount()<=(parent.getPoolSize())) {
    
    
        return super.offer(o);
    }

    //if we have less threads than maximum force creation of a new thread
    if (parent.getPoolSize()<parent.getMaximumPoolSize()) {
    
    
        return false;
    }

    //if we reached here, we need to add it to the queue
    return super.offer(o);
}

Especial en:

  • Al unirse a la cola, solo únase a la cola si el número de subprocesos en el grupo de subprocesos es igual al número máximo de grupos de subprocesos
  • Al unirse a la cola, si el número de subprocesos en el grupo de subprocesos es menor que el número máximo de grupos de subprocesos, se devolverá falso, lo que indica que no se pudo unir a la cola.

Esto controla el grupo de subprocesos de Tomcat al enviar una tarea:

  • Aún así, primero determinará si el número de subprocesos es menor que el número de subprocesos centrales. Si es menor que el número de subprocesos principales, cree un subproceso.
  • Si es igual al número de subprocesos principales, se unirá a la cola. Sin embargo, si el número de subprocesos es menor que el número máximo de subprocesos, la unión a la cola fallará y se crearán subprocesos.

Por lo tanto, cuando se envía la tarea, los subprocesos se crearán primero y no se unirán a la cola hasta que el número de subprocesos sea igual al número máximo de subprocesos.

Por supuesto, existe una lógica relativamente detallada: al enviar una tarea, si la cantidad de tareas que se procesan es menor que la cantidad de subprocesos en el grupo de subprocesos, se agregará directamente a la cola sin crear un subproceso, que es el getSubmittedCount en el código fuente anterior.

Cómo establecer la cantidad de subprocesos principales y la cantidad máxima de subprocesos en el grupo de subprocesos

Hay dos parámetros muy importantes en el grupo de subprocesos:

  • corePoolSize: el número de subprocesos principales, que indica el número de subprocesos residentes en el grupo de subprocesos
  • máximoPoolSize: el número máximo de subprocesos, que indica el número máximo de subprocesos que se pueden abrir en el grupo de subprocesos

Entonces, ¿cómo configurar estos dos parámetros?

Las tareas que somos responsables de ejecutar mediante el grupo de subprocesos se dividen en tres situaciones:

  • Tareas que requieren un uso intensivo de la CPU, como encontrar números primos del 1 al 1.000.000
  • Tareas intensivas de IO, como IO de archivos y IO de red
  • tareas mixtas

  Las características de las tareas con uso intensivo de CPU son que los subprocesos siempre utilizarán la CPU al ejecutar tareas, por lo que en este caso se debe evitar en la medida de lo posible el cambio de contexto de subprocesos.
  Por ejemplo, mi computadora ahora solo tiene una CPU. Si dos subprocesos ejecutan la tarea de encontrar números primos al mismo tiempo, entonces la CPU necesita realizar un cambio de contexto de subproceso adicional para lograr el efecto de paralelismo de subprocesos. En este momento, el se están ejecutando dos subprocesos El tiempo total es:
  tiempo de ejecución de la tarea 2 + tiempo de cambio de contexto del subproceso
  . Si solo hay un subproceso y este subproceso realiza dos tareas, entonces el tiempo es:
  tiempo de ejecución de la tarea
2.
  Entonces, para tareas con uso intensivo de CPU, el La cantidad de subprocesos es mejor igual al número de núcleos de la CPU, puede obtener el número de núcleos de su computadora a través de la siguiente API:

Runtime.getRuntime().availableProcessors()

Sin embargo, para responder a las solicitudes de bloqueo de subprocesos causadas por fallas de página u otras excepciones durante la ejecución del subproceso, podemos configurar un subproceso adicional, de modo que cuando un subproceso no necesite temporalmente la CPU, pueda haber un subproceso sustituto para continuar. utilizar la CPU.

Por lo tanto, para tareas que requieren un uso intensivo de la CPU, podemos establecer el número de subprocesos en: número de núcleos de CPU + 1

Veamos las tareas de tipo IO. Cuando los subprocesos realizan tareas de tipo IO, es posible que estén bloqueados en IO la mayor parte del tiempo. Si ahora hay 10 CPU, si solo configuramos 10 subprocesos para realizar tareas de tipo IO, entonces Será muy difícil. Tal vez estos 10 subprocesos estén bloqueados en IO, por lo que estas 10 CPU no tienen trabajo que hacer. Por lo tanto, para las tareas de IO, generalmente configuramos el número de subprocesos en: 2 * número de núcleos de CPU.

Sin embargo, incluso si está configurado en 2*número de núcleos de CPU, puede que no sea óptimo. Por ejemplo, si hay 10 CPU y el número de subprocesos es 20, entonces es posible que estos 20 subprocesos estén bloqueados en IO en el al mismo tiempo, para que pueda agregar más subprocesos, reduciendo así la utilización de la CPU.

Por lo general, si el tiempo de ejecución de las tareas de tipo IO es mayor, entonces puede haber más subprocesos bloqueados en IO al mismo tiempo y podemos configurar más subprocesos. Sin embargo, más subprocesos definitivamente no son mejores. Podemos usar lo siguiente
fórmula Para calcular:
Número de subprocesos = Número de núcleos de CPU * (1 + tiempo de espera de subprocesos / tiempo total de ejecución de subprocesos)

  • Tiempo de espera del subproceso: se refiere al tiempo en que el subproceso no usa la CPU, como el bloqueo en IO
  • Tiempo total de ejecución del subproceso: se refiere al tiempo total que tarda un subproceso en completar una tarea

Análisis de código fuente de propiedades y métodos básicos en el grupo de subprocesos.

En el código fuente del grupo de subprocesos, se utiliza una variable ctl de tipo AtomicInteger para representar el estado del grupo de subprocesos y la cantidad de subprocesos en funcionamiento en el grupo de subprocesos actual.
Un número entero ocupa 4 bytes, que son 32 bits. El grupo de subprocesos tiene 5 estados:

  • EN EJECUCIÓN: el grupo de subprocesos se está ejecutando normalmente y puede aceptar y procesar tareas normalmente.
  • APAGADO: El grupo de subprocesos está cerrado y no puede aceptar nuevas tareas. Sin embargo, el grupo de subprocesos ejecutará las tareas restantes en la cola de bloqueo. Una vez procesadas las tareas restantes, se interrumpirán todos los subprocesos de trabajo.
  • DETENER: el grupo de subprocesos se ha detenido, no puede aceptar nuevas tareas y no procesará tareas en la cola de bloqueo, lo que interrumpirá todos los subprocesos de trabajo.
  • ORDENAR: Después de que se detengan todos los subprocesos de trabajo en el grupo de subprocesos actual, se ingresará ORDENAR.
  • TERMINADO: Después de que el grupo de subprocesos esté en el estado ORDENADO, ejecutará el método terminado (). Después de la ejecución, ingresará al estado TERMINADO. En ThreadPoolExecutor, terminado () es un método vacío. Puede personalizar el grupo de subprocesos para anularlo. este método.

2 bits pueden representar 4 estados, y esos 5 estados requieren al menos tres bits, por ejemplo, así se representa en el código fuente del grupo de subprocesos:

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

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;

Integer.SIZE es 32, por lo que COUNT_BITS es 29. El sistema secundario final correspondiente a cada estado es:

  • EN EJECUCIÓN: 11100000 00000000 00000000 00000000
  • APAGADO: 00000000 00000000 00000000 00000000
  • DETENER: 00100000 00000000 00000000 00000000
  • ORDENAR: 01000000 00000000 00000000 00000000
  • TERMINADO: 01100000 00000000 00000000 00000000

Por lo tanto, solo necesita usar los tres bits más altos de un número entero para representar el estado de 5 grupos de subprocesos, y los 29 bits restantes se pueden usar para representar el número de subprocesos en funcionamiento . Por ejemplo, si el ctl es: 11100000 00000000 00000000 00001010, significa que el estado del grupo de subprocesos está EN EJECUCIÓN y actualmente hay 10 subprocesos trabajando en el grupo de subprocesos. "Trabajando" aquí significa que el subproceso está activo, ya sea ejecutando tareas o bloqueando y esperando tareas.

Al mismo tiempo, el grupo de subprocesos también proporciona algunos métodos para obtener el estado del grupo de subprocesos y la cantidad de subprocesos en funcionamiento, como por ejemplo:

// 29,二进制为00000000 00000000 00000000 00011101
private static final int COUNT_BITS = Integer.SIZE - 3;

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

// ~CAPACITY为11100000 00000000 00000000 00000000
// &操作之后,得到就是c的高3位
private static int runStateOf(int c)     {
    
     
    return c & ~CAPACITY; 
}

// CAPACITY为00011111 11111111 11111111 11111111
// &操作之后,得到的就是c的低29位
private static int workerCountOf(int c)  {
    
     
    return c & CAPACITY; 
}

Al mismo tiempo, existe otro método:

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

Es un método que se utiliza para combinar el estado de ejecución y el número de subprocesos de trabajo. Sin embargo, los dos números int pasados ​​a este método son limitados. Los 29 bits inferiores de rs deben ser 0 y los 3 bits superiores de wc deben ser 0. , por lo que después de la operación OR, se puede obtener el ctl preciso.

Al mismo tiempo, existen algunos métodos relacionados.

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;

// c状态是否小于s状态,比如RUNNING小于SHUTDOWN
private static boolean runStateLessThan(int c, int s) {
    
    
    return c < s;
}

// c状态是否大于等于s状态,比如STOP大于SHUTDOWN
private static boolean runStateAtLeast(int c, int s) {
    
    
    return c >= s;
}

// c状态是不是RUNNING,只有RUNNING是小于SHUTDOWN的
private static boolean isRunning(int c) {
    
    
    return c < SHUTDOWN;
}

// 通过cas来增加工作线程数量,直接对ctl进行加1
// 这个方法没考虑是否超过最大工作线程数的(2的29次方)限制,源码中在调用该方法之前会进行判断的
private boolean compareAndIncrementWorkerCount(int expect) {
    
    
    return ctl.compareAndSet(expect, expect + 1);
}

// 通过cas来减少工作线程数量,直接对ctl进行减1
private boolean compareAndDecrementWorkerCount(int expect) {
    
    
    return ctl.compareAndSet(expect, expect - 1);
}

ejecutar método

Al ejecutar el método de ejecución del grupo de subprocesos:

public void execute(Runnable command) {
    
    
    
    if (command == null)
        throw new NullPointerException();
    
    // 获取ctl
    // ctl初始值是ctlOf(RUNNING, 0),表示线程池处于运行中,工作线程数为0
    int c = ctl.get();
    
    // 工作线程数小于corePoolSize,则添加工作线程,并把command作为该线程要执行的任务
    if (workerCountOf(c) < corePoolSize) {
    
    
        // true表示添加的是核心工作线程,具体一点就是,在addWorker内部会判断当前工作线程数是不是超过了corePoolSize
        // 如果超过了则会添加失败,addWorker返回false,表示不能直接开启新的线程来执行任务,而是应该先入队
        if (addWorker(command, true))
            return;
        
        // 如果添加核心工作线程失败,那就重新获取ctl,可能是线程池状态被其他线程修改了
        // 也可能是其他线程也在向线程池提交任务,导致核心工作线程已经超过了corePoolSize
        c = ctl.get();
    }
    
    // 线程池状态是否还是RUNNING,如果是就把任务添加到阻塞队列中
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        
        // 在任务入队时,线程池的状态可能也会发生改变
        // 再次检查线程池的状态,如果线程池不是RUNNING了,那就不能再接受任务了,就得把任务从队列中移除,并进行拒绝策略
        
        // 如果线程池的状态没有发生改变,仍然是RUNNING,那就不需要把任务从队列中移除掉
        // 不过,为了确保刚刚入队的任务有线程会去处理它,需要判断一下工作线程数,如果为0,那就添加一个非核心的工作线程
        // 添加的这个线程没有自己的任务,目的就是从队列中获取任务来执行
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    // 如果线程池状态不是RUNNING,或者线程池状态是RUNNING但是队列满了,则去添加一个非核心工作线程
    // 实际上,addWorker中会判断线程池状态如果不是RUNNING,是不会添加工作线程的
    // false表示非核心工作线程,作用是,在addWorker内部会判断当前工作线程数已经超过了maximumPoolSize,如果超过了则会添加不成功,执行拒绝策略
    else if (!addWorker(command, false))
        reject(command);
}

método addWorker

El método addWorker es el método principal y se utiliza para agregar subprocesos. El parámetro principal indica si se agrega un subproceso principal o un subproceso no principal.
Antes de ver este método, es mejor que lo analicemos nosotros mismos primero. ¿Qué es agregar un hilo?
De hecho, necesita iniciar un subproceso. Ya sea un subproceso central o un subproceso no central, en realidad es solo un subproceso ordinario. La diferencia entre núcleo y no central es: si desea agregar un subproceso de trabajo
central , debe determinar el número actual de subprocesos de trabajo. Si excede corePoolSize.
Si no es así, se iniciará un nuevo subproceso de trabajo directamente para realizar la tarea.
Si se excede, no se iniciará un nuevo subproceso de trabajo, pero la tarea se pondrá en cola.
Si desea agregar un subproceso de trabajo no central, debe juzgar la situación actual. Si el número de subprocesos de trabajo excede el tamaño máximo de Pool.
De lo contrario, se iniciará un nuevo subproceso de trabajo para realizar la tarea directamente.
Si se excede, se rechazará la ejecución de la tarea,
por lo tanto, en el método addWorker, primero es necesario determinar si el hilo de trabajo ha excedido el límite, si no excede el límite, entonces Para iniciar un hilo.

Y en el método addWorker, debe determinar el estado del grupo de subprocesos. Si el estado del grupo de subprocesos no es EN EJECUCIÓN, entonces no es necesario agregar subprocesos. Por supuesto, hay un caso especial, es decir, el El estado del grupo de subprocesos es APAGADO, pero en la cola Si hay una tarea, aún necesita agregar un subproceso en este momento.

¿Cómo surgió entonces este caso especial?

Lo que mencionamos anteriormente es iniciar un nuevo hilo de trabajo, entonces, ¿cómo reciclar el hilo de trabajo? Es imposible que el subproceso de trabajo abierto esté siempre vivo, porque si el número de tareas disminuye de más a menos, no habrá necesidad de demasiados recursos de subproceso, por lo que habrá un mecanismo en el grupo de subprocesos para reciclar el iniciado. hilo de trabajo. Cómo reciclarlo se discutirá más adelante. Como se mencionó, primero analicemos si es posible que todos los hilos en el grupo de hilos hayan sido reciclados. La respuesta es sí.

En primer lugar, es comprensible que los subprocesos de trabajo no centrales se reciclen, pero ¿deberían reciclarse los subprocesos de trabajo centrales? De hecho, el significado de la existencia del grupo de subprocesos es generar recursos de subprocesos por adelantado y usarlos directamente cuando se necesita un subproceso. No es necesario abrir temporalmente el subproceso, por lo que, en circunstancias normales, el trabajador central abierto No es necesario reciclar el subproceso, incluso si no está disponible temporalmente. Si la tarea necesita procesarse, no es necesario reciclarla. Simplemente deje que el subproceso de trabajo principal espere allí.

¡pero! Existe un parámetro de este tipo en el grupo de subprocesos: enableCoreThreadTimeOut, que indica si el subproceso de trabajo central puede expirar, lo que significa si el subproceso de trabajo central puede reciclarse. El parámetro predeterminado es falso, pero podemos llamar a enableCoreThreadTimeOut (boolean valor) para cambiar este parámetro a verdadero. Siempre que se cambie, el subproceso de trabajo central también se reciclará, por lo que todos los subprocesos de trabajo en el grupo de subprocesos se pueden reciclar. Luego, si todos los subprocesos de trabajo se reciclan, se ejecutará una tarea. viene en la cola de bloqueo, esto crea casos especiales.

Supongo que te gusta

Origin blog.csdn.net/beautybug1126/article/details/132072664
Recomendado
Clasificación