Otro tipo de grupo de subprocesos es "almacenado en caché". Estos grupos de subprocesos se usan con la misma frecuencia que los fijos.

Como lo indica el nombre, este tipo de grupo de subprocesos almacena en caché los subprocesos. Mantiene vivos los subprocesos no utilizados durante un tiempo limitado para reutilizarlos para realizar nuevas tareas. Tal grupo de subprocesos es mejor para cuando tenemos una cantidad razonable de trabajo ligero.

El significado de "una cantidad razonable" es bastante amplio, pero debe saber que dicho grupo no es adecuado para todas las tareas. Por ejemplo, supongamos que queremos crear un millón de tareas. Incluso si cada uno toma una cantidad de tiempo muy pequeña, seguiremos utilizando una cantidad irrazonable de recursos y degradando el rendimiento. También debemos evitar estos grupos cuando el tiempo de ejecución es impredecible, por ejemplo, con tareas de E/S.

Bajo el capó, se llama al constructor ThreadPoolExecutor con los siguientes argumentos:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
      new SynchronousQueue<Runnable>());
}

Los siguientes valores se pasan al constructor como argumentos:

Parámetro Valor
corePoolSize (cuántos subprocesos estarán listos (iniciados) cuando se inicie el servicio ejecutor ) 0
MaximumPoolSize (la cantidad máxima de subprocesos que puede crear un servicio ejecutor ) Entero.MAX_VALUE
keepAliveTime (el tiempo que un subproceso liberado seguirá viviendo antes de ser destruido si la cantidad de subprocesos es mayor que corePoolSize ) 60L
unidad (unidades de tiempo) UnidadTiempo.SEGUNDOS
workQueue (implementación de una cola) nueva SynchronousQueue<Ejecutable>()

Y podemos pasar nuestra propia implementación de ThreadFactory exactamente de la misma manera.

Hablemos de SynchronousQueue

La idea básica de una transferencia síncrona es bastante simple y, sin embargo, contraria a la intuición (es decir, la intuición o el sentido común le dicen que está mal): puede agregar un elemento a una cola si y solo si otro subproceso recibe el elemento en el Mismo tiempo. En otras palabras, una cola síncrona no puede tener tareas en ella, porque tan pronto como llega una nueva tarea, el subproceso de ejecución ya ha recogido la tarea .

Cuando una nueva tarea ingresa a la cola, si hay un subproceso activo libre en el grupo, entonces recoge la tarea. Si todos los subprocesos están ocupados, se crea un nuevo subproceso.

Un grupo en caché comienza con cero subprocesos y potencialmente puede crecer hasta subprocesos Integer.MAX_VALUE . Esencialmente, el tamaño de un grupo de subprocesos en caché está limitado solo por los recursos del sistema.

Para conservar los recursos del sistema, los grupos de subprocesos en caché eliminan los subprocesos que están inactivos durante un minuto.

Veamos cómo funciona en la práctica. Crearemos una clase de tarea que modele una solicitud de usuario:

public class Task implements Runnable {
   int taskNumber;

   public Task(int taskNumber) {
       this.taskNumber = taskNumber;
   }

   @Override
   public void run() {
       System.out.println("Processed user request #" + taskNumber + " on thread " + Thread.currentThread().getName());
   }
}

En el método principal , creamos newCachedThreadPool y luego agregamos 3 tareas para su ejecución. Aquí imprimimos el estado de nuestro servicio (1) .

A continuación, hacemos una pausa de 30 segundos, comenzamos otra tarea y mostramos el estado (2) .

Después de eso, pausamos nuestro hilo principal durante 70 segundos, imprimimos el estado (3) , luego agregamos nuevamente 3 tareas y nuevamente imprimimos el estado (4) .

En lugares donde mostramos el estado inmediatamente después de agregar una tarea, primero agregamos una suspensión de 1 segundo para obtener resultados actualizados.

ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 3; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(1)

        TimeUnit.SECONDS.sleep(30);

        executorService.submit(new Task(3));
        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(2)

        TimeUnit.SECONDS.sleep(70);

            System.out.println(executorService);	//(3)

        for (int i = 4; i < 7; i++) {
            executorService.submit(new Task(i));
        }

        TimeUnit.SECONDS.sleep(1);
            System.out.println(executorService);	//(4)
        executorService.shutdown();

Y aquí está el resultado:

Solicitud de usuario procesada #0 en subproceso pool-1-thread-1
Solicitud de usuario procesada #1 en subproceso pool-1-thread-2
Solicitud de usuario procesada #2 en subproceso pool-1-thread-3
(1) java.util.concurrent .ThreadPoolExecutor@f6f4d33[En ejecución, tamaño del grupo = 3, subprocesos activos = 0, tareas en cola = 0, tareas completadas = 3]
Solicitud de usuario procesada n.º 3 en el subproceso pool-1-thread-2
(2) java.util.concurrent. ThreadPoolExecutor@f6f4d33[En ejecución, tamaño de grupo = 3, subprocesos activos = 0, tareas en cola = 0, tareas completadas = 4] (3) java.util.concurrent.ThreadPoolExecutor@f6f4d33[En ejecución, tamaño de grupo = 0, subprocesos
activos = 0 , tareas en cola = 0, tareas completadas = 4]
Solicitud de usuario procesada n.° 4 en el subproceso pool-1-thread-4
Solicitud de usuario procesada n.° 5 en el subproceso pool-1-thread-5
Solicitud de usuario procesada n.° 6 en el subproceso pool-1-thread-4
(4) java.util.concurrent.ThreadPoolExecutor@f6f4d33[En ejecución, tamaño del grupo = 2, subprocesos activos = 0, tareas en cola = 0, tareas completadas = 7]

Repasemos cada uno de los pasos:

Paso Explicación
1 (después de 3 tareas completadas) Creamos 3 subprocesos y se ejecutaron 3 tareas en estos tres subprocesos.
Cuando se muestra el estado, las 3 tareas están listas y los subprocesos están listos para realizar otras tareas.
2 (después de 30 segundos de pausa y ejecución de otra tarea) Después de 30 segundos de inactividad, los subprocesos aún están vivos y esperando tareas.
Se agrega y ejecuta otra tarea en un subproceso tomado del grupo de subprocesos activos restantes.
No se agregó ningún hilo nuevo al grupo.
3 (después de una pausa de 70 segundos) Los hilos se han eliminado del grupo.
No hay subprocesos listos para aceptar tareas.
4 (después de ejecutar 3 tareas más) Después de que se recibieron más tareas, se crearon nuevos hilos. Esta vez solo dos subprocesos lograron procesar 3 tareas.

Bueno, ahora estás familiarizado con la lógica de otro tipo de servicio ejecutor.

Por analogía con otros métodos de la clase de utilidad Executors , el método newCachedThreadPool también tiene una versión sobrecargada que toma un objeto ThreadFactory como argumento.