Ejecutores y Grupo de Hilos
Ya hemos explorado una variedad de mecanismos para soportar la multitarea, y los Executors son uno de ellos.
¿Qué son los Executors y el Thread Pooling?
Executors es un mecanismo que ofrece abstracciones de alto nivel para gestionar hilos. Permite crear y administrar un pool de hilos, que consiste en un conjunto de hilos preexistentes listos para ejecutar tareas. En lugar de crear un nuevo hilo para cada tarea, las tareas se envían al pool, donde su ejecución se distribuye entre los hilos.
¿Entonces, qué es exactamente un thread pool? Es una colección de hilos preexistentes listos para ejecutar tareas. Al utilizar un thread pool, se evita la sobrecarga de crear y destruir hilos repetidamente, ya que los mismos hilos pueden reutilizarse para múltiples tareas.
Si hay más tareas que hilos, las tareas esperan en la Task Queue. Un hilo disponible del pool toma una tarea de la cola, y una vez que la tarea se completa, el hilo toma una nueva tarea de la cola. Cuando todas las tareas en la cola se han completado, los hilos permanecen activos y esperan nuevas tareas.
Ejemplo de la vida real
Piense en un restaurante donde los cocineros (hilos) preparan pedidos (tareas). En lugar de contratar un nuevo cocinero para cada pedido, el restaurante emplea un número limitado de cocineros que atienden los pedidos a medida que llegan. Una vez que un cocinero termina un pedido, toma el siguiente, lo que ayuda a utilizar de manera eficiente los recursos del restaurante.
Método principal
newFixedThreadPool(int n): Crea un grupo con un número fijo de hilos igual a n.
Main.java
1ExecutorService executorService = Executors.newFixedThreadPool(20);
newCachedThreadPool(): Crea un grupo que puede crear nuevos hilos según sea necesario, pero reutilizará los hilos disponibles si los hay.
Main.java
1ExecutorService executorService = Executors.newCachedThreadPool();
newSingleThreadExecutor(): Crea un pool de un solo hilo que garantiza que las tareas se ejecuten de forma secuencial, es decir, una después de la otra. Es útil para tareas que deben ejecutarse en un orden estricto.
Main.java
1ExecutorService executorService = Executors.newSingleThreadExecutor();
En todos los ejemplos, los métodos de Executors devuelven una implementación de la interfaz ExecutorService, que se utiliza para gestionar hilos.
ExecutorService proporciona métodos para gestionar un grupo de hilos. Por ejemplo, submit(Runnable task) acepta una tarea como un objeto Runnable y la coloca en una cola para su ejecución. Devuelve un objeto Future, que puede utilizarse para verificar el estado de la tarea y obtener un resultado si la tarea produce un resultado.
Main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }
El método shutdown() inicia un apagado ordenado del grupo de hilos. Deja de aceptar nuevas tareas, pero completará las tareas actuales. Una vez que se llama a este método, el grupo no puede ser reiniciado.
El método awaitTermination(long timeout, TimeUnit unit) espera a que todas las tareas en el grupo finalicen dentro del período de tiempo especificado. Esta es una espera bloqueante que permite asegurar que todas las tareas se completen antes de finalizar el grupo.
Tampoco mencionamos la principal interfaz que ayuda a rastrear el estado del hilo, es la interfaz Future. El método submit() de la interfaz ExecutorService devuelve una implementación de la interfaz Future.
Si se desea obtener el resultado de la ejecución del hilo, se puede utilizar el método get(). Si el hilo implementa Runnable, el método get() no devuelve nada, pero si implementa Callable<T>, devuelve un tipo T.
Main.java
12345678910111213141516171819202122232425262728293031package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }
También se puede utilizar el método cancel(boolean mayInterruptIfRunning) para intentar cancelar la ejecución de una tarea. Si la tarea aún no ha comenzado, será cancelada. Si la tarea ya está en ejecución, puede ser interrumpida según el valor del parámetro mayInterruptIfRunning.
true: Si la tarea está en ejecución, será interrumpida llamando a Thread.interrupt() en el hilo que la ejecuta.
false: Si la tarea está en ejecución, no será interrumpida y el intento de cancelación no tendrá efecto sobre la tarea que se está ejecutando actualmente.
Bien y 2 métodos que intuitivamente se entiende lo que hacen:
isCancelled(): Verifica si la tarea ha sido cancelada;isDone(): Verifica si la tarea ha sido completada.
Ejemplo de uso
El tamaño del pool de hilos depende de la naturaleza de las tareas que se ejecutan. Normalmente, el tamaño del pool de hilos no debe estar codificado de forma fija; en su lugar, debe ser personalizable. El tamaño óptimo se determina monitorizando el rendimiento de las tareas ejecutadas.
Es más eficiente utilizar el número de threads = processor cores. Esto se puede observar en el código utilizando Runtime.getRuntime().availableProcessors().
Main.java
1int availableProcessors = Runtime.getRuntime().availableProcessors();
Diferencias entre crear hilos directamente y utilizar ExecutorService
Las principales diferencias entre crear hilos directamente y utilizar ExecutorService son la conveniencia y la gestión de recursos. La creación manual de hilos requiere administrar cada hilo de forma individual, lo que complica el código y la administración.
ExecutorService simplifica la gestión mediante el uso de un pool de hilos, facilitando el manejo de tareas. Además, mientras que la creación manual de hilos puede llevar a un alto consumo de recursos, ExecutorService permite personalizar el tamaño del pool de hilos.
¡Gracias por tus comentarios!
Pregunte a AI
Pregunte a AI
Pregunte lo que quiera o pruebe una de las preguntas sugeridas para comenzar nuestra charla
Can you explain more about how ExecutorService manages threads?
What are some best practices for using thread pools?
How does the Future interface help in managing task results?
Awesome!
Completion rate improved to 3.33
Ejecutores y Grupo de Hilos
Desliza para mostrar el menú
Ya hemos explorado una variedad de mecanismos para soportar la multitarea, y los Executors son uno de ellos.
¿Qué son los Executors y el Thread Pooling?
Executors es un mecanismo que ofrece abstracciones de alto nivel para gestionar hilos. Permite crear y administrar un pool de hilos, que consiste en un conjunto de hilos preexistentes listos para ejecutar tareas. En lugar de crear un nuevo hilo para cada tarea, las tareas se envían al pool, donde su ejecución se distribuye entre los hilos.
¿Entonces, qué es exactamente un thread pool? Es una colección de hilos preexistentes listos para ejecutar tareas. Al utilizar un thread pool, se evita la sobrecarga de crear y destruir hilos repetidamente, ya que los mismos hilos pueden reutilizarse para múltiples tareas.
Si hay más tareas que hilos, las tareas esperan en la Task Queue. Un hilo disponible del pool toma una tarea de la cola, y una vez que la tarea se completa, el hilo toma una nueva tarea de la cola. Cuando todas las tareas en la cola se han completado, los hilos permanecen activos y esperan nuevas tareas.
Ejemplo de la vida real
Piense en un restaurante donde los cocineros (hilos) preparan pedidos (tareas). En lugar de contratar un nuevo cocinero para cada pedido, el restaurante emplea un número limitado de cocineros que atienden los pedidos a medida que llegan. Una vez que un cocinero termina un pedido, toma el siguiente, lo que ayuda a utilizar de manera eficiente los recursos del restaurante.
Método principal
newFixedThreadPool(int n): Crea un grupo con un número fijo de hilos igual a n.
Main.java
1ExecutorService executorService = Executors.newFixedThreadPool(20);
newCachedThreadPool(): Crea un grupo que puede crear nuevos hilos según sea necesario, pero reutilizará los hilos disponibles si los hay.
Main.java
1ExecutorService executorService = Executors.newCachedThreadPool();
newSingleThreadExecutor(): Crea un pool de un solo hilo que garantiza que las tareas se ejecuten de forma secuencial, es decir, una después de la otra. Es útil para tareas que deben ejecutarse en un orden estricto.
Main.java
1ExecutorService executorService = Executors.newSingleThreadExecutor();
En todos los ejemplos, los métodos de Executors devuelven una implementación de la interfaz ExecutorService, que se utiliza para gestionar hilos.
ExecutorService proporciona métodos para gestionar un grupo de hilos. Por ejemplo, submit(Runnable task) acepta una tarea como un objeto Runnable y la coloca en una cola para su ejecución. Devuelve un objeto Future, que puede utilizarse para verificar el estado de la tarea y obtener un resultado si la tarea produce un resultado.
Main.java
12345678910111213141516171819202122232425262728293031323334package com.example; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { // Create a thread pool with 5 threads ExecutorService executor = Executors.newFixedThreadPool(5); // Define the task to be executed Runnable task = () -> { System.out.println("Task is running: " + Thread.currentThread().getName()); try { Thread.sleep(2000); // Simulate some work } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Task completed: " + Thread.currentThread().getName()); }; // Submit the task for execution and get a `Future` Future<?> future = executor.submit(task); // Check if the task is done System.out.println("Is task done? " + future.isDone()); // You can use `future` to check the status of the task or wait for its completion // Example: future.get() - blocks until the task is completed (not used in this example) // Initiate an orderly shutdown of the executor service executor.shutdown(); } }
El método shutdown() inicia un apagado ordenado del grupo de hilos. Deja de aceptar nuevas tareas, pero completará las tareas actuales. Una vez que se llama a este método, el grupo no puede ser reiniciado.
El método awaitTermination(long timeout, TimeUnit unit) espera a que todas las tareas en el grupo finalicen dentro del período de tiempo especificado. Esta es una espera bloqueante que permite asegurar que todas las tareas se completen antes de finalizar el grupo.
Tampoco mencionamos la principal interfaz que ayuda a rastrear el estado del hilo, es la interfaz Future. El método submit() de la interfaz ExecutorService devuelve una implementación de la interfaz Future.
Si se desea obtener el resultado de la ejecución del hilo, se puede utilizar el método get(). Si el hilo implementa Runnable, el método get() no devuelve nada, pero si implementa Callable<T>, devuelve un tipo T.
Main.java
12345678910111213141516171819202122232425262728293031package com.example; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class Main { public static void main(String[] args) { ExecutorService executor = Executors.newFixedThreadPool(3); // Callable task that returns a result Callable<String> task = () -> { Thread.sleep(1000); // Simulate some work return "Task result"; }; Future<String> future = executor.submit(task); try { // Get the result of the task String result = future.get(); System.out.println("Task completed with result: " + result); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } executor.shutdown(); } }
También se puede utilizar el método cancel(boolean mayInterruptIfRunning) para intentar cancelar la ejecución de una tarea. Si la tarea aún no ha comenzado, será cancelada. Si la tarea ya está en ejecución, puede ser interrumpida según el valor del parámetro mayInterruptIfRunning.
true: Si la tarea está en ejecución, será interrumpida llamando a Thread.interrupt() en el hilo que la ejecuta.
false: Si la tarea está en ejecución, no será interrumpida y el intento de cancelación no tendrá efecto sobre la tarea que se está ejecutando actualmente.
Bien y 2 métodos que intuitivamente se entiende lo que hacen:
isCancelled(): Verifica si la tarea ha sido cancelada;isDone(): Verifica si la tarea ha sido completada.
Ejemplo de uso
El tamaño del pool de hilos depende de la naturaleza de las tareas que se ejecutan. Normalmente, el tamaño del pool de hilos no debe estar codificado de forma fija; en su lugar, debe ser personalizable. El tamaño óptimo se determina monitorizando el rendimiento de las tareas ejecutadas.
Es más eficiente utilizar el número de threads = processor cores. Esto se puede observar en el código utilizando Runtime.getRuntime().availableProcessors().
Main.java
1int availableProcessors = Runtime.getRuntime().availableProcessors();
Diferencias entre crear hilos directamente y utilizar ExecutorService
Las principales diferencias entre crear hilos directamente y utilizar ExecutorService son la conveniencia y la gestión de recursos. La creación manual de hilos requiere administrar cada hilo de forma individual, lo que complica el código y la administración.
ExecutorService simplifica la gestión mediante el uso de un pool de hilos, facilitando el manejo de tareas. Además, mientras que la creación manual de hilos puede llevar a un alto consumo de recursos, ExecutorService permite personalizar el tamaño del pool de hilos.
¡Gracias por tus comentarios!