Procesos en Java

Conceptos

Programa

Un programa es el conjunto de instrucciones (líneas de código en nuestro caso) para realizar una acción. Un ejemplo sencillo sería una receta de cocina, que te dice cómo tienes que hacer para realizar la comida, pero no te va a llenar el plato.

Procesos

Un proceso es un programa en ejecución. Por lo tanto un proceso es la actividad. Respecto al ejemplo anterior, si el programa es la receta de cocina, el proceso sería a la persona realizando la receta y así terminar con un plato de comida hecho.

Los procesos se pueden ejecutan de forma concurrente. Esto significa que mientras se ejecuta un proceso se pueden estar realizando otros procesos al mismo tiempo dentro del sistema.

Procesos con Java

Ejecutar procesos del sistema desde la JVM

Desde java se pueden ejecutar procesos en el sistema como si los ejecutáramos como comandos en la terminal (salvo que el árbol de dependencias de los procesos tendría como padre al proceso de java). Hay 2 clases principalmente para crear estos procesos: Runtime y ProcessBuilder. Ambas clases son gestionadas mediante  la clase Process.

Process

Es una clase abstracta que permite controlar los procesos nativos del sistema que devuelven las clases Runtime y ProcessBuilder.

Runtime

En cualquier aplicación de java se instancia esta clase ya que permite interactuar a la aplicación con el entorno en el que se encuentra ejecutándose.

La salida sería la siguiente:

ProcessBuilder

La clase ProcessBuilder maneja los siguientes atributos del proceso: comando, entorno, directorio de trabajo, recurso de entrada estándar, el destino de la salida estándar redireccionar la salida estándar de error.

Al ejecutar este código la salida va hacia el fichero output.txt al cual redirigimos la salida en la línea 16:

Procesos de java

En ésta categoría entran una clase y una interfaz, Thread y Runnable respectivamente. Cuando hablamos de procesos propios de java estaremos indicando que lo crearemos nosotros mediante código y lo ejecutaremos de la misma forma. En ambos casos, al crear la clase tendremos que sobreescribir el metodo run() que son las instrucciones que realizará el proceso cuando lo inicialicemos.

Thread

Al tratarse de una clase extendemos nuestra clase a ésta y sobreescribimos el método run() para que realice algo al iniciarlo. Para esto instanciamos nuestra clase en el main y llamamos al método start(), ojo con equivocarse con esto.

Un método interesante de la clase es el método sleep, básicamente pone a «dormir» el proceso durante el tiempo en milisegundos que pongas como parámetro. Con esto hay que fijarse que si una clase no hereda Thread la forma de llamar a este método aparece en la línea 12.

Runnable

Al tratarse de una interfaz hay que implementarla y despues implementar el método run(), ahora mostraré el programa anterior hecho de esta forma:

Que opción es mejor? Depende de como quieras estructurar tu programa. Hay que tener en cuenta que en java no existe la herencia múltiple, por lo que en caso de querer implementar como un hilo una clase que ya herede de otra tendrás que utilizar la interfaz Runnable, en caso contrario podrás utilizar la forma que más te guste.

Sincronización de procesos

La sincronización de procesos es necesaria cuando los procesos compiten por un recurso compartido o cooperan entre ellos para comunicar información, normalmente de esto se encarga el propio sistema operativo que utilicemos mediante los mecanismos de sincronización.

Al trabajar con procesos y querer sincronizarlos hay que conocer lo que son las secciones críticas (SC): son los recursos compartidos entre los procesos en los que no pueden acceder mas de un proceso. Sus propiedades principales son:

-Exclusión mutua: garantiza que si un proceso está dentro de la SC no vaya a entrar ningún otro.

-No interbloqueo: ningún proceso fuera de la SC puede negar la entrada a este.

-No inanición: los procesos tienen que poder entrar en algún momento.

-Independencia del hardware: no hacer suposiciones del número de procesadores o de la velocidad relativa de los procesos.

Semáforos

Los procesos mirarán una variable flag que se situará en la sección crítica a la que acceden y dependiendo del valor de esta se pondran a esperar o seguirán ejecutandose. Cuando un proceso salga de la sección crítica habrá modificado la variable y notificará a los demás procesos que pueden entrar. Por lo que se produciría una condición de carrera que manejaría el SO.

Los semáforos son una abstracción mas general de lo anterior, en los que permite almacenar los eventos ya producidos y despertar a un proceso en concreto. Un semáforo lleva asociado una cola de procesos, para bloquear o despertar según sea preciso.

Los semáforos tienen dos operaciones básicas: bajar(s) – acquire() en java, subir(s) – release() en java.

Monitores

Los monitores son estructuras de un lenguaje de programación que ofrecen una funcionalidad equivalente a la de los semáforos y que son más fáciles de controlar. Un módulo monitor encapsula la mutua exclusión de datos y procedimientos que pueden acceder a los datos protegidos. Los usuarios pueden hacer llamadas a estos procedimientos usando al monitor como una tabla de estado para determinar cuando proceder y cuando suspender operaciones.

Para implementarlo en java, hay que añadir el modificador de acceso synchronazed a los bloques de código que accedan al recurso compartido (Object) y que queramos exclusión mutua. Hay que tener cuidado con esto porque esto afecta a todos los bloques que implementen el modificador y accedan al recurso que llaman. La forma de solucionar esto es mediante waits y notifys.

Mensajes

Cuando se trata de intercambiar información entre distintos es utiliza un modelo de paso de mensajes, básicamente es trabajar sobre una cola en la que el que envía añade elementos y el que recibe los saca, que llamaremos buzón.

Existen 3 tipos de buzones según su capacidad:

-Ilimitada: buzón ideal con capacidad infinita.

-Limitada: el proceso que envía puede encontrarse con el buzón lleno.

-Nula: no se puede almacenar ningún mensaje, por lo que los procesos tienen que sinconizarse para cada comunicación, el primero que llega tiene que esperar al segundo en el punto de comunicación.

Proyecto

Aquí dejo un link a github de un proyecto en el que implemento las tres formas de sincronización de procesos en java.

Realizaré unas aclaraciones sobre el proyecto de arriba:

– Las clases waiter, cooker y client son los threads que tienen que sincronizar para el correcto funcionamiento del programa.

– Las clases restaurant y kitchen realizan las sincronización mediante semáforos.

– La clase stacks es un monitor y también realiza la sincronización mediante mensajes con capacidad ilimitada (al utilizar la clase LinkedList<> como recurso compartido).

– Este programa tiene un fallo de estructura que se puede arreglar de 3 formas, dejo al lector el pensar donde está y cambiarlo en su ordenador.

– Pueden escribir en los comentarios cuales creen que son las posibles soluciones