03. Explicación detallada del uso de la clase ThreadPoolExecutor del grupo de subprocesos

Tenemos que crear subprocesos cuando están en uso y destruirlos cuando no están en uso. Un uso y destrucción tan frecuentes de subprocesos consumirá mucho tiempo y recursos. ¿Cómo podemos evitar crear y destruir subprocesos con frecuencia cuando se utilizan subprocesos?

Esto requiere tecnología de grupo de subprocesos: coloque varios subprocesos en un grupo y el grupo gestiona la creación, el uso y la destrucción de estos subprocesos.

ThreadPoolExecutor

La clase principal del grupo de subprocesos: ThreadPoolExecutor.

1. La relación de herencia de la clase principal de ThreadPoolExecutor:

1. ThreadPoolExecutor hereda AbstractExecutorService (clase abstracta)

2. AbstractExecutorService (clase abstracta), que implementa la interfaz ExecutorService.

3.ExecutorService hereda la interfaz Executor

Executor es una interfaz de nivel superior, en la que solo se declara un método de ejecución (Runnable), el valor de retorno es nulo y el parámetro es de tipo Runnable. Se puede entender por el significado literal de que se usa para ejecutar la tarea. aprobada en;

La interfaz ExecutorService hereda la interfaz Executor y declara algunos métodos: submit, invokeAll, invokeAny, shutDown, etc .;

La clase abstracta AbstractExecutorService implementa la interfaz ExecutorService y básicamente implementa todos los métodos declarados en ExecutorService;

2. El método de construcción de ThreadPoolExecutor:

public class ThreadPoolExecutor extends AbstractExecutorService {
    
    
   
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler);
 
    public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler);
    
}

Introducción a los parámetros del método de construcción:

  • corePoolSize: El tamaño del grupo de núcleos Este parámetro tiene una gran relación con el principio de implementación del grupo de subprocesos que se describe más adelante. Una vez creado el grupo de subprocesos, de forma predeterminada, no hay subprocesos en el grupo de subprocesos. En su lugar, espera a que lleguen las tareas antes de crear subprocesos para realizar tareas, a menos que se invoquen los métodos prestartAllCoreThreads () o prestartCoreThread (). vea en el nombre, significa subprocesos creados previamente, es decir, subprocesos corePoolSize o un subproceso se crean antes de que no llegue ninguna tarea. De forma predeterminada, después de que se crea el grupo de subprocesos, el número de subprocesos en el grupo de subprocesos es 0. Cuando llega una tarea, se creará un subproceso para ejecutar la tarea. Cuando el número de subprocesos en el grupo de subprocesos alcance corePoolSize, alcance Las tareas se colocan en la cola de caché;
  • maximumPoolSize: el número máximo de subprocesos permitidos en el grupo de subprocesos. Si la cola de bloqueo actual está llena y la tarea continúa enviándose, se crea un nuevo subproceso para realizar la tarea, siempre que el número actual de subprocesos sea menor que maximumPoolSize; cuando la cola de bloqueo es una cola ilimitada, maximumPoolSize no funciona porque no se puede enviar al grupo de subprocesos principal El subproceso se colocará continuamente en workQueue.
  • keepAliveTime: indica cuánto tiempo se mantiene el hilo como máximo cuando no hay una ejecución de la tarea que terminará. De forma predeterminada, keepAliveTime funcionará solo cuando el número de subprocesos en el grupo de subprocesos sea mayor que corePoolSize, hasta que el número de subprocesos en el grupo de subprocesos no sea mayor que corePoolSize, es decir, cuando el número de subprocesos en el grupo de subprocesos sea mayor que corePoolSize, si un subproceso está inactivo Cuando el tiempo llegue a keepAliveTime, se terminará hasta que el número de subprocesos en el grupo de subprocesos no exceda corePoolSize. Pero si se llama al método allowCoreThreadTimeOut (boolean), cuando el número de subprocesos en el grupo de subprocesos no es mayor que corePoolSize, el parámetro keepAliveTime también funcionará hasta que el número de subprocesos en el grupo de subprocesos sea 0;
  • unit: La unidad de tiempo del parámetro keepAliveTime. Hay 7 valores. Hay 7 propiedades estáticas en la clase TimeUnit:
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
 
  • workQueue: una cola de bloqueo utilizada para almacenar tareas en espera de ser ejecutadas. La elección de este parámetro también es muy importante y tendrá un impacto significativo en el proceso en ejecución del grupo de subprocesos. En términos generales, hay varias opciones para la cola de bloqueo aquí :
ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;

ArrayBlockingQueue y PriorityBlockingQueue se utilizan con menos frecuencia, y LinkedBlockingQueue y Synchronous se utilizan generalmente. La estrategia de cola del grupo de subprocesos está relacionada con BlockingQueue.

  • threadFactory: fábrica de hilos, utilizado principalmente para crear hilos;
  • handler: Indica la estrategia cuando se rechaza el procesamiento de la tarea, con los siguientes cuatro valores:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 

3. Hay varios métodos importantes en la clase ThreadPoolExecutor:

1.ejecutar ()

El método execute () es en realidad el método declarado en Executor, que se implementa en ThreadPoolExecutor. Este método es el método principal de ThreadPoolExecutor. A través de este método, una tarea puede ser enviada al grupo de subprocesos y ejecutada por el grupo de subprocesos.

2.submit ()

El método submit () es un método declarado en ExecutorService. AbstractExecutorService ya tiene una implementación específica. No se reescribe en ThreadPoolExecutor. Este método también se usa para enviar tareas al grupo de subprocesos, pero el método y ejecutar (The) es diferente. Puede devolver el resultado de la ejecución de la tarea. Si observa la implementación del método submit (), encontrará que en realidad es el método execute () llamado, pero usa Future para obtener el resultado de la ejecución de la tarea.

3.shutdown () 和 shutdownNow ()

  • shutdown (): el grupo de subprocesos no se terminará inmediatamente, pero se terminará después de que se hayan ejecutado todas las tareas en la cola de caché de tareas, pero no se aceptarán nuevas tareas
  • shutdownNow (): termina inmediatamente el grupo de subprocesos e intenta interrumpir la tarea que se está ejecutando, y borra la cola de caché de tareas, vuelve a la tarea que aún no se ha ejecutado

En la clase ThreadPoolExecutor, el método de envío de la tarea principal es el método execute (). Aunque la tarea también se puede enviar a través de submit, la llamada final en el método de envío es el método execute ():

public void execute(Runnable command) {
    
    
    if (command == null)
        throw new NullPointerException();
    //1.获取当前正在运行的线程个数
    int c = ctl.get();
    //2.判断当前线程数是否小于核心线程数
    if (workerCountOf(c) < corePoolSize) {
    
    
        //2.1 新建一个线程执行任务
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    //3.核心池已满,但任务队列未满,添加到队列中
    if (isRunning(c) && workQueue.offer(command)) {
    
    
        //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
        int recheck = ctl.get();
        if (!isRunning(recheck) && remove(command))
            //3.2如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务
            reject(command);
        else if (workerCountOf(recheck) == 0)
            //3.3如果之前的线程已被销毁完,新建一个线程
            addWorker(null, false);
    }
    else if (!addWorker(command, false)) //4.核心池已满,队列已满,试着创建一个新线程
        reject(command);//如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务
}

4. Lista de atributos principales de la clase ThreadPoolExecutor

private final BlockingQueue<Runnable> workQueue;              //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
private volatile long  keepAliveTime;    //线程存活时间   
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize; //线程池支持的最大线程数
 
private volatile int   poolSize;       //线程池中当前的线程数
 
private volatile RejectedExecutionHandler handler; //任务拒绝策略
 
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
 
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
 
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

5. Cola de caché de tareas y estrategia de puesta en cola

Hemos mencionado la cola de caché de tareas muchas veces antes, es decir, workQueue, que se usa para almacenar tareas en espera de ser ejecutadas.

El tipo de workQueue es BlockingQueue, que normalmente se puede seleccionar entre los siguientes tres tipos:

1) ArrayBlockingQueue: cola de primero en entrar, primero en salir basada en matriz, el tamaño de esta cola debe especificarse cuando se crea;

2) LinkedBlockingQueue: cola de primero en entrar, primero en salir basada en la lista enlazada. Si el tamaño de la cola no se especifica al crearlo, el valor predeterminado será Integer.MAX_VALUE;

3) SynchronousQueue: esta cola es especial, no guardará las tareas enviadas, pero creará directamente un nuevo hilo para ejecutar nuevas tareas.

6. Estrategia de rechazo de tareas

Cuando la cola de caché de tareas del grupo de subprocesos está llena y el número de subprocesos en el grupo de subprocesos alcanza el tamaño máximo de la agrupación, si hay tareas por venir, se adoptará la estrategia de rechazo de tareas. Por lo general, existen las siguientes cuatro estrategias:

  • ThreadPoolExecutor.AbortPolicy: Descarta la tarea y lanza RejectedExecutionException.
  • ThreadPoolExecutor.DiscardPolicy: también descarta tareas, pero no lanza excepciones.
  • ThreadPoolExecutor.DiscardOldestPolicy: Descarte la primera tarea en la cola y luego intente ejecutar la tarea nuevamente (repita este proceso)
  • ThreadPoolExecutor.CallerRunsPolicy: el grupo de subprocesos rechaza la tarea y el subproceso que llama al método de ejecución la ejecuta.

7. Métodos de fábrica habituales

Para facilitar el uso de grupos de subprocesos, se proporcionan varios métodos de fábrica para grupos de subprocesos en Executors. De esta manera, muchos novatos no necesitan saber mucho sobre ThreadPoolExecutor. Solo necesitan usar directamente los métodos de fábrica de Executors para usar Thread Piscina:

  • newFixedThreadPool: Este método devuelve un número fijo de grupos de subprocesos, el número de subprocesos permanece sin cambios, cuando se envía una tarea, si el grupo de subprocesos está libre, se ejecutará inmediatamente, si no, se suspenderá en una cola de tareas, esperando a que se ejecute el hilo libre.
  • newSingleThreadExecutor: Cree un grupo de subprocesos, si está inactivo, ejecútelo, si no hay ningún subproceso inactivo, se suspenderá en la cola de tareas.
  • newCachedThreadPool: Devuelve un grupo de subprocesos que puede ajustar el número de subprocesos de acuerdo con la situación real, no limita el número máximo de subprocesos, si se utilizan subprocesos inactivos, se ejecutan tareas y si no hay tareas, no se crean subprocesos. Y cada hilo inactivo se reciclará automáticamente después de 60 segundos
  • newScheduledThreadPool: crea un grupo de subprocesos que puede especificar el número de subprocesos, pero este grupo de subprocesos también tiene la función de retrasar y la ejecución periódica de tareas, similar a los temporizadores.

7. Cómo configurar razonablemente el tamaño del grupo de subprocesos

Cómo configurar el tamaño del grupo de subprocesos de manera razonable, generalmente necesita configurar el tamaño del grupo de subprocesos de acuerdo con el tipo de tarea:

1. Si se trata de una tarea que consume mucha CPU, debe exprimir la CPU tanto como sea posible y el valor de referencia se puede establecer en N CPU + 1

2. Si se trata de una tarea intensiva en E / S, el valor de referencia se puede establecer en 2 * N CPU
 
para ofrecerle una fórmula agradable:

  • Número óptimo de subprocesos = ((tiempo de espera del subproceso + tiempo de CPU del subproceso) / tiempo de CPU del subproceso) * número de CPU

Por ejemplo: el número de núcleo de la CPU de nuestro servidor es de 4 núcleos, una CPU de subproceso de tarea tarda 20 ms, el subproceso en espera (E / S de red, E / S de disco) tarda 80 ms, luego el número óptimo de hilos: (80 + 20) / 20 * 4 = 20. Es decir, el número óptimo de subprocesos se establece en 20.

Supongo que te gusta

Origin blog.csdn.net/weixin_43828467/article/details/114323138
Recomendado
Clasificación