ExecutorService es el nombre que se le ha dado a la API de concurrencia implantada en el JDK7. Su nombre deriva de la única interfaz que hereda de Executor.
Hemos hablado más en profundidad en este blog sobre el patrón Command y hoy venimos a desenmarañar la relación de las clases de esta API para ver una vez más, como el JDK no es ajeno a la implantación de patrones.
Esta API no solo implementa el patrón Command, también usa el Method Factory para devolvernos una instancia. No reflejaremos esto para no complicar más de lo necesario el diagrama UML.
Comencemos por la clase ThreadPoolExecutor. Por supuesto, simplificaremos su explicación, ya que la clase es un pequeño infierno de código usando clases muy variopintas.
Este ThreadPoolExecutor que tomamos como ejemplo, es una de las posibilidades que tenemos a la hora de instanciar un pool de threads de tipo ExecutorService.
Esta clase contiene un atributo de tipo BlockingQueue, que básicamente permite que un hilo introduzca tareas en la cola y otro las consuma de forma segura.
También contiene una inner-class llamada Worker. Esta clase tiene un constructor parametrizado con un Runnable que se encarga de mantener el control de los hilos que ejecutan las tareas. Se podría interpretar como la clase encargada de ejecutar las tareas de la BlockingQueue.
Cuando llamamos al método execute() de la clase principal (recordemos que ThreadPoolExecutor contiene una clase hija Worker), lo que realmente hacemos es comprobar el número de Workers y el máximo que podemos tener para ejecutar la tarea.
- Si tenemos menos Workers de los que podríamos tener, añadimos uno para ejecutar la tarea.
- SI tenemos el máximo numero de Workers ya trabajando, metemos en la blockingQueue esa tarea para ser ejecutada más adelante.
- SI no podemos crear más Workers ni poner en la cola esa tarea, se crea un nuevo hilo solo para desecharla.
A continuación pongo un diagrama UML muy sencillo en el que se aprecian las principales clases, interfaces y relaciones.
¿Veis el parecido con el patrón Command? El invoker aquí esta representado por la clase ThreadPoolExecutor, la cual a través de sus Workers, va resolviendo tareas que el usuario crea.
Recordemos el propósito fundamental del patrón Command: desacoplar la ejecución de la clase que ejecuta una tarea del usuario que la crea, para ser gestionada por un intermediario.
La API Concurrence efectivamente usa el patrón Command, por supuesto de una forma más refinada (ya que usa mecanismos de control de sincronicidad, entre ostras cosas) pero no deja de cumplir con el principio del patrón.