Grupo de subprocesos de concurrencia de Java

Autor original: Matrix Hai Zi 

Dirección original: programación concurrente de Java: el uso del grupo de subprocesos

 

En el artículo anterior, creamos un hilo cuando usamos un hilo. Esto es muy fácil de implementar, pero habrá un problema:

  Si hay una gran cantidad de subprocesos concurrentes, y cada subproceso ejecuta una tarea a corto plazo y finaliza, la creación frecuente de subprocesos reducirá en gran medida la eficiencia del sistema, porque se necesita tiempo para crear y destruir subprocesos con frecuencia.

  Entonces, ¿hay alguna manera de hacer que los subprocesos sean reutilizables, es decir, después de ejecutar una tarea, no se destruirá, pero puede continuar realizando otras tareas?

  En Java, este efecto se puede lograr a través de un grupo de subprocesos. Hoy explicaremos el grupo de subprocesos en Java en detalle. Primero, comenzamos con los métodos en la clase principal ThreadPoolExecutor, y luego explicamos su principio de implementación, luego damos un ejemplo de su uso y finalmente discutimos cómo configurarlo razonablemente El tamaño del grupo de subprocesos.

1. Clase ThreadPoolExecutor en Java

  La clase java.uitl.concurrent.ThreadPoolExecutor es la clase principal en el grupo de subprocesos, por lo que si desea comprender a fondo el grupo de subprocesos en Java, primero debe comprender esta clase. Echemos un vistazo al código fuente de implementación específico de la clase ThreadPoolExecutor.

  Se proporcionan cuatro métodos de construcción en la clase 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);
    ...
}

Como se puede ver en el código anterior, ThreadPoolExecutor hereda la clase AbstractExecutorService y proporciona cuatro constructores.De hecho, al observar la implementación específica del código fuente de cada constructor, se encuentra que los primeros tres constructores son los cuartos constructores llamados Trabajo de inicialización realizado por el dispositivo.

   A continuación se explica el significado de cada parámetro en el constructor:

  • 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 ningún subproceso 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 llame al método prestartAllCoreThreads () o prestartCoreThread (). Desde estos dos métodos Como puede ver en el nombre, significa subprocesos creados previamente, es decir, los 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 Coloque las tareas en la cola de caché;
  • maximumPoolSize: el número máximo de subprocesos en el grupo de subprocesos, este parámetro también es un parámetro muy importante, indica el número máximo de subprocesos que se pueden crear en el grupo de subprocesos;
  • 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 hilo está inactivo Cuando el tiempo llegue a keepAliveTime, se terminará hasta que el número de hilos en el grupo de hilos no supere 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.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒
  • workQueue: Una cola de bloqueo que se utiliza 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 puesta en 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:由调用线程处理该任务 

   La relación entre la configuración de parámetros específicos y el grupo de subprocesos se describirá en la siguiente sección.

  A partir del código de la clase ThreadPoolExecutor dado anteriormente, podemos saber que ThreadPoolExecutor hereda AbstractExecutorService. Echemos un vistazo a la implementación de AbstractExecutorService:

public abstract class AbstractExecutorService implements ExecutorService {
 
     
    protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) { };
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) { };
    public Future<?> submit(Runnable task) {};
    public <T> Future<T> submit(Runnable task, T result) { };
    public <T> Future<T> submit(Callable<T> task) { };
    private <T> T doInvokeAny(Collection<? extends Callable<T>> tasks,
                            boolean timed, long nanos)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException {
    };
    public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                           long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException {
    };
    public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                         long timeout, TimeUnit unit)
        throws InterruptedException {
    };
}

AbstractExecutorService es una clase abstracta que implementa la interfaz ExecutorService.

  Luego miramos la implementación de la interfaz ExecutorService:

public interface ExecutorService extends Executor {
 
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
        throws InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                                  long timeout, TimeUnit unit)
        throws InterruptedException;
 
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Y ExecutorService hereda la interfaz Executor, echemos un vistazo a la implementación de la interfaz Executor:

public interface Executor {
    void execute(Runnable command);
}

   En este punto, todos deben comprender la relación entre ThreadPoolExecutor, AbstractExecutorService, ExecutorService y 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 que se utiliza para ejecutar la tarea. aprobada en;

  Luego, 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;

  Entonces ThreadPoolExecutor hereda la clase AbstractExecutorService.

  Hay varios métodos muy importantes en la clase ThreadPoolExecutor:

execute()
submit()
shutdown()
shutdownNow()

   El método execute () es en realidad un método declarado en Executor. 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.

  El método submit () es un método declarado en ExecutorService. Se ha implementado en AbstractExecutorService. No se reescribe en ThreadPoolExecutor. Este método también se usa para enviar tareas al grupo de subprocesos, pero el método y execute () es diferente, es puede devolver el resultado de la ejecución de la tarea, observe 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 (el contenido relacionado con Future aparecerá en el siguiente artículo).

  shutdown () y shutdownNow () se utilizan para cerrar el grupo de subprocesos.

  Hay muchos otros métodos:

  Por ejemplo: getQueue (), getPoolSize (), getActiveCount (), getCompletedTaskCount () y otros métodos para obtener atributos relacionados con el grupo de subprocesos. Los amigos que estén interesados ​​pueden consultar la API por sí mismos.

2. Análisis en profundidad del principio de implementación del grupo de subprocesos

  En la sección anterior, presentamos ThreadPoolExecutor desde un nivel macro. Analicemos el principio de implementación específico del grupo de subprocesos en profundidad y expliquemos a partir de los siguientes aspectos: estado del grupo de subprocesos, ejecución de tareas, inicialización de subprocesos en el grupo de subprocesos, cola de caché de tareas y cola estrategia, estrategia de rechazo de tareas, cierre del grupo de subprocesos, ajuste dinámico de la capacidad del grupo de subprocesos

1. Estado del grupo de subprocesos

  Una variable volátil se define en ThreadPoolExecutor, y se definen varias variables finales estáticas para representar los diversos estados del grupo de subprocesos:

//表示当前线程池的状态,它是一个volatile变量用来保证线程之间的可见性;
volatile int runState;

//下面的几个static final变量表示runState可能的几个取值。

//当创建线程池后,初始时,线程池处于RUNNING状态;
static final int RUNNING    = 0;

//如果调用了shutdown()方法,则线程池处于SHUTDOWN状态,此时线程池不能够接受新的任务,它会等待所有任务执行完毕;
static final int SHUTDOWN   = 1;

//如果调用了shutdownNow()方法,则线程池处于STOP状态,此时线程池不能接受新的任务,并且会去尝试终止正在执行的任务;
static final int STOP       = 2;

//当线程池处于SHUTDOWN或STOP状态,并且所有工作线程已经销毁,任务缓存队列已经清空或执行结束后,线程池被设置为TERMINATED状态。
static final int TERMINATED = 3;

2. Ejecución de tareas

  Antes de comprender todo el proceso desde el envío de la tarea al grupo de subprocesos hasta la finalización de la ejecución de la tarea, echemos un vistazo a algunas otras variables miembro importantes en 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;   //用来记录已经执行完毕的任务个数

Se ha señalado el papel de cada variable, aquí nos centraremos en explicar las tres variables corePoolSize, maximumPoolSize y largerPoolSize.

corePoolSize se traduce al tamaño del grupo de núcleos en muchos lugares. De hecho, mi comprensión es el tamaño del grupo de subprocesos. Para dar un ejemplo simple: si hay una fábrica, hay 10 trabajadores en la fábrica y cada trabajador solo puede hacer una tarea a la vez. Por lo tanto, mientras haya trabajadores entre los 10 trabajadores que están inactivos, las tareas que vengan se asignarán a los trabajadores inactivos; cuando 10 trabajadores tienen tareas por hacer, si aún quedan tareas, las tareas se pondrán en cola; si decir El número de nuevas tareas está creciendo a un ritmo mucho más rápido de lo que los trabajadores pueden realizar. En este momento, el supervisor de la fábrica puede querer remediar medidas, como contratar nuevamente a 4 trabajadores temporales; luego asignar tareas a estos 4 trabajadores temporales; si Hablar de la velocidad de la tarea de 14 trabajadores todavía no es suficiente, en este momento el supervisor de fábrica puede tener que considerar no aceptar nuevas tareas o abandonar algunas de las anteriores. Cuando algunos de estos 14 trabajadores están libres y la tasa de crecimiento de las nuevas tareas es relativamente lenta, el supervisor de la fábrica puede considerar renunciar a 4 trabajadores temporales y quedarse con los originales 10. Después de todo, cuesta dinero contratar trabajadores adicionales.

El corePoolSize en este ejemplo es 10 y el maximumPoolSize es 14 (10 + 4). En otras palabras, corePoolSize es el tamaño del grupo de subprocesos En mi opinión, maximumPoolSize es un remedio para el grupo de subprocesos, es decir, un remedio cuando el volumen de tareas es repentinamente demasiado grande. Sin embargo, para facilitar la comprensión, corePoolSize se traducirá al tamaño del grupo principal más adelante en este artículo.

largePoolSize es solo una variable utilizada para la grabación, utilizada para registrar la mayor cantidad de subprocesos en el grupo de subprocesos, y no tiene nada que ver con la capacidad del grupo de subprocesos.

Pasemos al tema y echemos un vistazo al proceso de la tarea desde el envío hasta la ejecución final.

En la clase ThreadPoolExecutor, el método de envío de la tarea principal es el método execute (). Aunque la tarea se puede enviar a través de submit, de hecho, la llamada final en el método de envío es el método execute (), por lo que solo necesitamos estudiar el Método execute () El principio de realización puede ser:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) {
        if (runState == RUNNING && workQueue.offer(command)) {
            if (runState != RUNNING || poolSize == 0)
                ensureQueuedTaskHandled(command);
        }
        else if (!addIfUnderMaximumPoolSize(command))
            reject(command); // is shutdown or saturated
    }
}

Puede que el código anterior no parezca tan fácil de entender, vamos a explicarlo frase por frase:

Primero, determine si el comando de tarea enviado es nulo; si es nulo, se lanzará una excepción de puntero nulo;

Seguida de esta oración, esta oración debe entenderse:

if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))

Debido a que es un operador condicional o, primero calcule el valor de la primera mitad. Si el número actual de subprocesos en el grupo de subprocesos no es menor que el tamaño del grupo principal, entonces ingresará directamente el siguiente bloque de instrucciones if.

Si el número actual de subprocesos en el grupo de subprocesos es menor que el tamaño del grupo principal, ejecute la segunda mitad, que se ejecutará

addIfUnderCorePoolSize(command)

  Si el método addIfUnderCorePoolSize devuelve falso después de la ejecución, continúe ejecutando el siguiente bloque de instrucciones if; de lo contrario, todo el método se ejecutará directamente.

  Si el método addIfUnderCorePoolSize devuelve falso después de la ejecución, continúe juzgando:

if (runState == RUNNING && workQueue.offer(command))

Si el grupo de subprocesos actual está en el estado EN EJECUCIÓN, la tarea se coloca en la cola de caché de tareas; si el grupo de subprocesos actual no está en el estado EN EJECUCIÓN o la tarea no se coloca en la cola de caché, ejecute:

addIfUnderMaximumPoolSize(command)

  Si el método addIfUnderMaximumPoolSize falla, se ejecuta el método accept () para rechazar la tarea.

  De vuelta al frente:

if (runState == RUNNING && workQueue.offer(command))

   La ejecución de esta oración, si se dice que el grupo de subprocesos actual está en estado EJECUTANDO y la tarea se colocó con éxito en la cola de caché de tareas, entonces continúe juzgando:

if (runState != RUNNING || poolSize == 0)

   Esta oración es una medida de emergencia para evitar que otros subprocesos llamen repentinamente al método shutdown o shutdownNow para cerrar el grupo de subprocesos mientras agregan esta tarea a la cola de caché de tareas. Si es así, ejecute:

ensureQueuedTaskHandled(command)

   Para el tratamiento de emergencia, se puede ver en el nombre para garantizar que se procesen las tareas agregadas a la cola de caché de tareas.

  Luego miramos la implementación de dos métodos clave: addIfUnderCorePoolSize y addIfUnderMaximumPoolSize:

private boolean addIfUnderCorePoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < corePoolSize && runState == RUNNING)
            t = addThread(firstTask);        //创建线程去执行firstTask任务   
        } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

Esta es la implementación específica del método addIfUnderCorePoolSize, por el nombre se puede ver que su intención es ejecutar el método cuando el tamaño es menor que el tamaño del núcleo. Veamos su implementación específica. Primero, se adquiere el bloqueo, porque este lugar implica el cambio del estado del grupo de subprocesos. Primero, use la instrucción if para determinar si el número de subprocesos en el grupo de subprocesos actual es menor que el grupo principal tamaño. Algunos amigos pueden tener preguntas: ¿No se ha evaluado en el método ()? El método addIfUnderCorePoolSize se ejecutará solo cuando el número actual de subprocesos en el grupo de subprocesos sea menor que el tamaño del grupo principal. ¿Por qué este lugar continúa ¿juzgar? La razón es muy simple. No hay bloqueo en el proceso de evaluación anterior, por lo que poolSize puede ser menor que corePoolSize cuando el método de ejecución evalúa, y una vez que se completa la evaluación, las tareas se envían al grupo de subprocesos en otros subprocesos, lo que puede hacer que poolSize no sea menor que corePoolSize Por lo tanto, debe continuar juzgando en este lugar. Luego, determine si el estado del grupo de subprocesos es EN EJECUCIÓN, la razón también es muy simple, porque es posible llamar al método shutdown o shutdownNow en otros subprocesos. Entonces ejecuta

t = addThread(firstTask);

   Este método también es muy crítico, el parámetro pasado es la tarea enviada y el valor de retorno es de tipo Thread. Luego, juzgue si t está vacío. Si está vacío, indica que la creación del hilo falló (es decir, poolSize> = corePoolSize o runState no es igual a RUNNING), de lo contrario se llama al método t.start () para iniciar el hilo.

  Echemos un vistazo a la implementación del método addThread:

private Thread addThread(Runnable firstTask) {
    Worker w = new Worker(firstTask);
    Thread t = threadFactory.newThread(w);  //创建一个线程,执行任务   
    if (t != null) {
        w.thread = t;            //将创建的线程的引用赋值为w的成员变量       
        workers.add(w);
        int nt = ++poolSize;     //当前线程数加1       
        if (nt > largestPoolSize)
            largestPoolSize = nt;
    }
    return t;
}

   En el método addThread, primero cree un objeto Worker con la tarea enviada, luego llame a la fábrica de subprocesos threadFactory para crear un nuevo subproceso t, y luego asigne la referencia del subproceso t al subproceso de la variable miembro del objeto Worker, y luego pase los trabajadores. add (w) Añade el objeto Worker al conjunto de trabajo.

  Echemos un vistazo a la implementación de la clase Worker:

private final class Worker implements Runnable {
    private final ReentrantLock runLock = new ReentrantLock();
    private Runnable firstTask;
    volatile long completedTasks;
    Thread thread;
    Worker(Runnable firstTask) {
        this.firstTask = firstTask;
    }
    boolean isActive() {
        return runLock.isLocked();
    }
    void interruptIfIdle() {
        final ReentrantLock runLock = this.runLock;
        if (runLock.tryLock()) {
            try {
        if (thread != Thread.currentThread())
        thread.interrupt();
            } finally {
                runLock.unlock();
            }
        }
    }
    void interruptNow() {
        thread.interrupt();
    }
 
    private void runTask(Runnable task) {
        final ReentrantLock runLock = this.runLock;
        runLock.lock();
        try {
            if (runState < STOP &&
                Thread.interrupted() &&
                runState >= STOP)
            boolean ran = false;
            beforeExecute(thread, task);   //beforeExecute方法是ThreadPoolExecutor类的一个方法,没有具体实现,用户可以根据
            //自己需要重载这个方法和后面的afterExecute方法来进行一些统计信息,比如某个任务的执行时间等           
            try {
                task.run();
                ran = true;
                afterExecute(task, null);
                ++completedTasks;
            } catch (RuntimeException ex) {
                if (!ran)
                    afterExecute(task, ex);
                throw ex;
            }
        } finally {
            runLock.unlock();
        }
    }
 
    public void run() {
        try {
            Runnable task = firstTask;
            firstTask = null;
            while (task != null || (task = getTask()) != null) {
                runTask(task);
                task = null;
            }
        } finally {
            workerDone(this);   //当任务队列中没有任务时,进行清理工作       
        }
    }
}

En realidad, implementa la interfaz Runnable, por lo que el Thread t = threadFactory.newThread (w) anterior; tiene básicamente el mismo efecto que la siguiente oración:

Thread t = new Thread(w);

   Es equivalente a pasar una tarea ejecutable y ejecutar esta ejecución en el hilo t.

  Ahora que Worker implementa la interfaz Runnable, el método principal es, naturalmente, el método run ():

public void run() {
    try {
        Runnable task = firstTask;
        firstTask = null;
        while (task != null || (task = getTask()) != null) {
            runTask(task);
            task = null;
        }
    } finally {
        workerDone(this);
    }
}

   Se puede ver en la implementación del método run que primero ejecuta la tarea firstTask pasada a través del constructor. Después de llamar a runTask () para ejecutar la firstTask, usa continuamente getTask () en el bucle while para buscar nuevas tareas para su ejecución. Entonces, ¿dónde conseguirlo? Naturalmente, se toma de la cola de caché de tareas. GetTask es un método de la clase ThreadPoolExecutor, no un método de la clase Worker. La siguiente es la implementación del método getTask:

Runnable getTask() {
    for (;;) {
        try {
            int state = runState;
            if (state > SHUTDOWN)
                return null;
            Runnable r;
            if (state == SHUTDOWN)  // Help drain queue
                r = workQueue.poll();
            else if (poolSize > corePoolSize || allowCoreThreadTimeOut) //如果线程数大于核心池大小或者允许为核心池线程设置空闲时间,
                //则通过poll取任务,若等待一定的时间取不到任务,则返回null
                r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);
            else
                r = workQueue.take();
            if (r != null)
                return r;
            if (workerCanExit()) {    //如果没取到任务,即r为null,则判断当前的worker是否可以退出
                if (runState >= SHUTDOWN) // Wake up others
                    interruptIdleWorkers();   //中断处于空闲状态的worker
                return null;
            }
            // Else retry
        } catch (InterruptedException ie) {
            // On interruption, re-check runState
        }
    }
}

   En getTask, primero determine el estado actual del grupo de subprocesos, si runState es mayor que SHUTDOWN (es decir, STOP o TERMINATED), luego devuelva directamente null.

  Si runState está APAGADO o EN EJECUCIÓN, la tarea se obtiene de la cola de caché de tareas.

  Si el número de subprocesos en el grupo de subprocesos actual es mayor que el tamaño del grupo principal corePoolSize o si se permite establecer el tiempo de supervivencia inactivo para los subprocesos en el grupo principal, llame a poll (time, timeUnit) para recuperar la tarea. Este método esperará un tiempo determinado. Si la tarea no se puede recuperar, devuelve nulo.

  Luego juzgue si la tarea recuperada r es nula. Si es nula, juzgue si el trabajador actual puede salir llamando al método workerCanSalir (). Veamos la implementación de workerCanSalir ():

private boolean workerCanExit() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    boolean canExit;
    //如果runState大于等于STOP,或者任务缓存队列为空了
    //或者  允许为核心池线程设置空闲存活时间并且线程池中的线程数目大于1
    try {
        canExit = runState >= STOP ||
            workQueue.isEmpty() ||
            (allowCoreThreadTimeOut &&
             poolSize > Math.max(1, corePoolSize));
    } finally {
        mainLock.unlock();
    }
    return canExit;
}

   Es decir, si el grupo de subprocesos está en el estado STOP, o la cola de tareas está vacía, o si se permite establecer el tiempo de supervivencia inactivo para los subprocesos del grupo principal y el número de subprocesos es mayor que 1, el trabajador es permitido salir. Si al trabajador se le permite salir, llame a interruptIdleWorkers () para interrumpir al trabajador inactivo. Veamos la implementación de interruptIdleWorkers ():

void interruptIdleWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)  //实际上调用的是worker的interruptIfIdle()方法
            w.interruptIfIdle();
    } finally {
        mainLock.unlock();
    }
}

   Se puede ver en la implementación que en realidad llama al método interruptIfIdle () del trabajador, en el método interruptIfIdle () del trabajador:

void interruptIfIdle() {
    final ReentrantLock runLock = this.runLock;
    if (runLock.tryLock()) {    //注意这里,是调用tryLock()来获取锁的,因为如果当前worker正在执行任务,锁已经被获取了,是无法获取到锁的
                                //如果成功获取了锁,说明当前worker处于空闲状态
        try {
    if (thread != Thread.currentThread())  
    thread.interrupt();
        } finally {
            runLock.unlock();
        }
    }
}

    Hay un método de diseño muy inteligente. Si diseñamos un grupo de subprocesos, puede haber un subproceso de despacho de tareas. Cuando un subproceso se encuentra inactivo, una tarea se toma de la cola de la caché de tareas al subproceso inactivo para su ejecución. Pero aquí, este método no se adopta, porque administrará adicionalmente el subproceso de despacho de tareas, lo que aumentará de manera invisible la dificultad y la complejidad. Aquí, el subproceso que ha completado la tarea se envía directamente a la cola de caché de tareas para recuperar la tarea para ejecución.

   Veamos la implementación del método addIfUnderMaximumPoolSize. La idea de implementación de este método es muy similar a la idea de implementación del método addIfUnderCorePoolSize. La única diferencia es que el método addIfUnderMaximumPoolSize es que el número de subprocesos en el grupo de subprocesos alcanza el tamaño del grupo principal y la adición de tareas a la cola de tareas falla. En las circunstancias:

private boolean addIfUnderMaximumPoolSize(Runnable firstTask) {
    Thread t = null;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        if (poolSize < maximumPoolSize && runState == RUNNING)
            t = addThread(firstTask);
    } finally {
        mainLock.unlock();
    }
    if (t == null)
        return false;
    t.start();
    return true;
}

   Vea si no lo es, de hecho, es básicamente lo mismo que la implementación del método addIfUnderCorePoolSize, excepto que poolSize <maximumPoolSize en la condición de juicio de la sentencia if es diferente.

  En este punto, la mayoría de mis amigos deberían tener un conocimiento básico de todo el proceso desde que la tarea se envía al grupo de subprocesos hasta que se ejecuta. Aquí hay un resumen:

  1) Primero, debemos tener claro el significado de corePoolSize y maximumPoolSize;

  2) En segundo lugar, debemos saber para qué se utiliza el Trabajador;

  3) Para conocer la estrategia de procesamiento después de que la tarea se envía al grupo de subprocesos, aquí hay cuatro puntos principales:

  • Si el número de subprocesos en el grupo de subprocesos actual es menor que corePoolSize, cada vez que llega una tarea, se creará un subproceso para ejecutar la tarea;
  • Si el número de subprocesos en el grupo de subprocesos actual> = corePoolSize, entonces cada vez que llegue una tarea, intentará agregarla a la cola de caché de tareas. Si la adición es exitosa, la tarea esperará a que el subproceso inactivo la tome fuera para ejecución; si la adición falla (en general, la cola de caché de tareas está llena), intentará crear un nuevo hilo para ejecutar esta tarea;
  • Si el número de subprocesos en el grupo de subprocesos actual alcanza el maximumPoolSize, se adoptará la estrategia de rechazo de tareas para el procesamiento;
  • Si el número de subprocesos en el grupo de subprocesos es mayor que corePoolSize, si el tiempo de inactividad de un subproceso excede keepAliveTime, el subproceso se terminará hasta que el número de subprocesos en el grupo de subprocesos no sea mayor que corePoolSize; si se permite establecer el tiempo de supervivencia de los subprocesos en el grupo principal, luego el grupo principal. Si el tiempo de inactividad del subproceso excede keepAliveTime, el subproceso también se terminará.

3. Inicialización de subprocesos en el grupo de subprocesos

  De forma predeterminada, después de que se crea el grupo de subprocesos, no hay subprocesos en el grupo de subprocesos y el subproceso se creará después de que se envíe la tarea.

  En la práctica, si necesita crear subprocesos inmediatamente después de que se crea el grupo de subprocesos, puede hacerlo de las dos formas siguientes:

  • prestartCoreThread (): inicializa un hilo central;
  • prestartAllCoreThreads (): inicializa todos los subprocesos principales

  La siguiente es la implementación de estos dos métodos:

public boolean prestartCoreThread() {
    return addIfUnderCorePoolSize(null); //注意传进去的参数是null
}
 
public int prestartAllCoreThreads() {
    int n = 0;
    while (addIfUnderCorePoolSize(null))//注意传进去的参数是null
        ++n;
    return n;
}

 

Supongo que te gusta

Origin blog.csdn.net/sanmi8276/article/details/112779056
Recomendado
Clasificación