[Grupo de subprocesos] Estructura de ThreadPoolExecutor y parámetros de constructor detallados

Introducción al grupo de subprocesos

El problema de no usar el grupo de subprocesos

  • Es caro crear y destruir subprocesos repetidamente
  • Demasiados hilos ocupan mucha memoria

El uso de grupos de subprocesos es similar a la economía planificada, controlando la cantidad total de recursos y reutilizando subprocesos. Hay tres ventajas como sigue.

  1. Acelera la velocidad de respuesta. Elimina el retraso causado por la creación de subprocesos.
  2. Uso razonable de CPU y memoria. Controlar el número de subprocesos no causará desbordamiento de memoria debido a demasiados subprocesos, ni desperdiciará recursos de CPU debido a muy pocos subprocesos.
  3. Facilitar la gestión unificada de subprocesos. Por ejemplo, estadísticas.

Ejemplos de escenarios de uso de subprocesos:

  • El servidor recibe una gran cantidad de solicitudes y el uso del grupo de subprocesos puede reducir en gran medida el número de creación y destrucción de subprocesos y mejorar la eficiencia del servidor.
  • Cuando utilice varios subprocesos en desarrollo, considere usar un grupo de subprocesos.

Estructura del grupo de subprocesos

Inserte la descripción de la imagen aquí
ThreadPoolExecutor hereda AbstractExecutorService (implementa ExecutorService), las 4 clases / interfaces se pueden considerar como grupos de subprocesos y se transforman hacia arriba (polimorfismo). Hay cinco clases anidadas en ThreadPoolExecutor, cuatro de las cuales son las clases de implementación de la interfaz de estrategia de rechazo RejectedExecutionHandler, y una es la clase Worker, que se usa para mantener el estado de control de interrupciones del subproceso que está ejecutando la tarea, y otras clases secundarias información.

Executors es una clase de herramienta de grupo de subprocesos que puede implementar rápidamente grupos de subprocesos a través de métodos de fábrica estáticos, como Executors.newFixedThreadPool (10).

Tomemos un ejemplo de una panadería.
Supongamos que hay una panadería. La panadería suele tener 5 panaderos. A medida que aumentan los pedidos cada día, 5 maestros se unen gradualmente al trabajo. Los pedidos que no se puedan procesar a tiempo se colgarán en la columna de pedidos de la pared, esperando que el panadero los procese en orden.
Cuando hay demasiados pedidos, 5 panaderos están demasiado ocupados para contratar panaderos temporales, hasta 5 panaderos. El número de panaderos temporales se ajustará en función del número de tareas y se despedirá a los panaderos temporales que no tengan nada que hacer durante demasiado tiempo.
Después de que la tienda esté cerrada o el pedido llegue a 10 panaderos, no se aceptarán nuevos pedidos.

Constructor de grupos de subprocesos

Inserte la descripción de la imagen aquí
ThreadPoolExecutor tiene solo cuatro constructores, con un máximo de 7 parámetros. Entre ellos, los primeros 5 son parámetros necesarios, y la fábrica de hilos y la estrategia de rechazo son opcionales.

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) {
    
    
    this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
         Executors.defaultThreadFactory(), defaultHandler);
}

Inserte la descripción de la imagen aquí

Estrategia de adición de hilos

  1. Si el número de subprocesos es menor que corePoolSize, incluso si otros subprocesos de trabajo están inactivos, se seguirán creando nuevos subprocesos para ejecutar nuevas tareas. [Pregunta: ¿Se va a construir todo a la vez o uno por uno?]
  2. Si el número de subprocesos es mayor que corePoolSize pero menor que maxPoolSize, la tarea se coloca en la cola de bloqueo.
  3. Si la cola está llena y el número de subprocesos es menor que maxPoolSize, se crea un nuevo subproceso para ejecutar la tarea.
  4. Si la cola está llena y el número de subprocesos es mayor o igual que maxPoolSize, la tarea se rechaza.

Inserte la descripción de la imagen aquí

Las características de sumar o restar hilos.

  1. Si establece el mismo corePoolSize y maxThreadPoolSize, es equivalente a crear un grupo de subprocesos de tamaño fijo. En este momento, la cola de trabajo generalmente usa una cola ilimitada y el período de tiempo de espera es 0L.
  2. El grupo de subprocesos espera mantener el número de subprocesos pequeño y solo agregar nuevos subprocesos cuando la carga sea muy grande.
  3. Al establecer maxPoolSize en Integer.MAX_VALUE, puede permitir que el grupo de subprocesos se adapte a tareas simultáneas de cualquier capacidad.
  4. Solo cuando la cola esté llena, se crearán nuevos subprocesos con más de corePoolSize. Si se usa una cola ilimitada, como LinkedBlockingQueue, la cantidad de subprocesos no excederá corePoolSize.

Estrategia de rechazo del grupo de subprocesos

Tiempo de rechazo: vea las dos posiciones de llamada en el método de ejecución

final void reject(Runnable command) {
    
    
    handler.rejectedExecution(command, this);
}
  1. Cuando el Ejecutor está cerrado, se rechazará el envío de nuevas tareas;
  2. Cuando Executor usa límites limitados para el número máximo de subprocesos y capacidad de la cola de trabajo, y está saturado.

Estrategia de rechazo

  1. AbortPolicy: lanza una excepción directamente, lo que indica que el envío no fue exitoso
  2. DiscardPolicy: descarte la nueva tarea directamente sin lanzar una excepción.
  3. DiscardOldestPolicy: descartar la tarea más antigua
  4. CallerRunsPolicy: Deje que se ejecute el hilo que envió la tarea. La ventaja es evitar pérdidas comerciales; la velocidad de envío se reduce (el subproceso principal sigue enviando tareas, y cuando el grupo de subprocesos y la cola de trabajo están llenos, el subproceso principal comienza a ejecutar las tareas enviadas, lo que equivale a dar el grupo de subprocesos un tiempo de búfer).

Análisis de código fuente

public interface RejectedExecutionHandler {
    
    
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

Solo hay un método para rechazar la interfaz de políticas y el propósito es muy claro.

Las cuatro estrategias de rechazo implementan la interfaz RejectedExecutionHandler, y la implementación del código también es muy simple y clara. Aquí solo se presentan dos estrategias, DiscardOldestPolicy y CallerRunsPolicy.

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    
    
    public DiscardOldestPolicy() {
    
     }
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        // 1. 如果线程池是RUNNING状态,并且线程达到饱和,则丢弃最老的任务,重新执行execute方法
        // 2. 如果线程池非RUNNING状态,则直接丢弃任务。
        if (!e.isShutdown()) {
    
    
            e.getQueue().poll();
            e.execute(r);
        }
    }
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    
    
    public CallerRunsPolicy() {
    
     }
    // 1. 如果线程池是RUNNING状态,并且线程达到饱和,则使用调用者线程执行任务
    // 2. 如果线程池非RUNNING状态,则直接丢弃任务。
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    
    
        if (!e.isShutdown()) {
    
    
            r.run();
        }
    }
}

Sabemos que llamar directamente al método run () de runnable es una llamada sincrónica, y el hilo de llamada es el hilo que llama al método de ejecución del grupo de hilos actual.

Cola de trabajo

Hay tres estrategias generales para las colas de trabajo.

  • Cola de envío directo: el valor predeterminado es SynchronousQueue, que envía tareas directamente a los subprocesos sin guardarlas. Si no hay ningún hilo inactivo, el intento de agregar la tarea a la cola fallará y se creará un nuevo hilo. Por lo general, requieren un
    máximo de tamaños de piscina ilimitados .
  • Cola ilimitada: por ejemplo, LinkedBlockingQueue, que se utiliza para solicitudes de ráfagas.
  • Colas limitadas: como ArrayBlockingQueue, que ayuda a evitar el agotamiento de los recursos, y es difícil ajustar el valor del tamaño de la cola y el tamaño máximo del grupo.

Fábrica de hilos

public interface ThreadFactory {
    
    
    Thread newThread(Runnable r);
}

ThreadFactory tiene un solo método, que es una interfaz funcional.

static class DefaultThreadFactory implements ThreadFactory {
    
    
    // 标记线程池的数量
    private static final AtomicInteger poolNumber = new AtomicInteger(1);
    // 新线程所在线程组
    private final ThreadGroup group;
    // 标记线程的数量
    private final AtomicInteger threadNumber = new AtomicInteger(1);
    // 线程的命名前缀(所在线程池)
    private final String namePrefix;

    DefaultThreadFactory() {
    
    
        SecurityManager s = System.getSecurityManager();
        // 策略线程组或创建线程工厂的线程所在线程组
        group = (s != null) ? s.getThreadGroup() :
                              Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
                      poolNumber.getAndIncrement() +
                     "-thread-";
    }

    public Thread newThread(Runnable r) {
    
    
        Thread t = new Thread(group, r,
                              namePrefix + threadNumber.getAndIncrement(),
                              0);
        // 只生产用户线程
        if (t.isDaemon())
            t.setDaemon(false);
        // 线程的安全级别是5
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

Grupo de subprocesos común

Resumen de los parámetros del grupo de subprocesos generados por los ejecutores.
Inserte la descripción de la imagen aquí

La fábrica de subprocesos predeterminada ThreadFactory es Executors.defaultThreadFactory (), y la política de rechazo es AbortPolicy.

  • SingleThreadPool: Internal y FixedThreadPool son básicamente iguales, pero el número de subprocesos es diferente.
  • CachedThreadPool: crea un grupo de subprocesos almacenable en caché. Es un grupo de subprocesos ilimitado con la función de recuperar automáticamente subprocesos redundantes. Se adopta la cola de transferencia síncrona y las tareas se asignan directamente a los subprocesos inactivos o se crean nuevos subprocesos para su ejecución. Si el hilo está inactivo (no se ejecuta ninguna tarea) durante 60 segundos, el hilo se recicla.
  • ScheduledThreadPool: un grupo de subprocesos que admite el tiempo y la ejecución de tareas periódicas.
  • WorkStealingPool es un nuevo grupo de subprocesos en JDK8, una extensión de la nueva clase de grupo de subprocesos ForkJoinPool, que puede usar razonablemente la CPU para realizar operaciones paralelas en tareas y es adecuado para escenarios que consumen mucho tiempo, como la recursividad, dividir y conquistar, y escenarios desbloqueados.

Puntos a tener en cuenta sobre los grupos de subprocesos

Evite la acumulación de tareas, evite el aumento excesivo del número de subprocesos y solucione problemas de fugas de subprocesos. Cuando hay muchos subprocesos, puede haber pérdidas de subprocesos. Los subprocesos se ejecutan pero no se reciclan. Esto puede ser un problema de lógica de la tarea.

Supongo que te gusta

Origin blog.csdn.net/LIZHONGPING00/article/details/105155805
Recomendado
Clasificación