Programación

1º DAM/DAW - Curso 2023-2024

User Tools

Site Tools


apuntes:concurrencia

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Next revision
Previous revision
apuntes:concurrencia [2021/03/10 13:18] – created Santiago Faciapuntes:concurrencia [2023/05/28 23:59] (current) Santiago Faci
Line 3: Line 3:
 ===== Programación multihilo ====== ===== Programación multihilo ======
  
-\part*{Programación concurrente} +==== Multiproceso ====
- +
-\section{Introducción a la programación multihilo} +
- +
-\subsection{Multiproceso}+
  
 El multiproceso consiste en la ejecución de varios procesos diferentes de forma simultánea para la realización de una o varias tareas relacionadas o no entre sí. En este caso, cada uno de estos procesos es una aplicación independiente. El caso más conocido es aquel en el que nos referimos al Sistema Operativo (Windows, Linux, MacOS, . . .) y decimos que es multitarea puesto que es capaz de ejecutar varias tareas o procesos (o programas) al mismo tiempo. El multiproceso consiste en la ejecución de varios procesos diferentes de forma simultánea para la realización de una o varias tareas relacionadas o no entre sí. En este caso, cada uno de estos procesos es una aplicación independiente. El caso más conocido es aquel en el que nos referimos al Sistema Operativo (Windows, Linux, MacOS, . . .) y decimos que es multitarea puesto que es capaz de ejecutar varias tareas o procesos (o programas) al mismo tiempo.
  
-\subsection{Multihilo}+==== Multihilo ====
  
 Hablamos de multihilo cuando se ejecutan varias tareas relacionadas o no entre sí dentro de una misma aplicación. En este caso no son procesos diferentes sino que dichas tareas se ejecutan dentro del mismo proceso del Sistema Operativo. A cada una de estas tareas se le conoce como hilo o thread (en algunos contextos también como procesos ligeros). Hablamos de multihilo cuando se ejecutan varias tareas relacionadas o no entre sí dentro de una misma aplicación. En este caso no son procesos diferentes sino que dichas tareas se ejecutan dentro del mismo proceso del Sistema Operativo. A cada una de estas tareas se le conoce como hilo o thread (en algunos contextos también como procesos ligeros).
Line 17: Line 13:
 En ambos casos estaríamos hablando de lo que se conoce como Programación Concurrente. Hay que tener en cuenta que en ninguno de los dos casos la ejecución es realmente simultánea, ya que el Sistema Operativo es quién hace que parezca así, pero los ejecuta siguiendo lo que se conoce como algoritmos de planificación. En ambos casos estaríamos hablando de lo que se conoce como Programación Concurrente. Hay que tener en cuenta que en ninguno de los dos casos la ejecución es realmente simultánea, ya que el Sistema Operativo es quién hace que parezca así, pero los ejecuta siguiendo lo que se conoce como algoritmos de planificación.
  
-\subsection{Algoritmos de planificación}+==== Algoritmos de planificación ====
  
 En entornos multitarea, un algoritmo de planificación indica la forma en que el tiempo de procesamiento debe repartirse entre todas las tareas que deben ejecutarse en un momento determinado. Existen diferentes algoritmos de planificación, cada uno con sus ventajas e inconvenientes, pero todos intentan cumplir con los siguientes puntos: En entornos multitarea, un algoritmo de planificación indica la forma en que el tiempo de procesamiento debe repartirse entre todas las tareas que deben ejecutarse en un momento determinado. Existen diferentes algoritmos de planificación, cada uno con sus ventajas e inconvenientes, pero todos intentan cumplir con los siguientes puntos:
  
-\begin{itemize} +  * Debe ser imparcial y eficiente 
-    \item Debe ser imparcial y eficiente +  Debe minimizar el tiempo de respuesta al usuario, sobre todo en aquellos procesos o tareas más interactivas 
-    \item Debe minimizar el tiempo de respuesta al usuario, sobre todo en aquellos procesos o tareas más interactivas +  Debe ejecutar el mayor número de procesos 
-    \item Debe ejecutar el mayor número de procesos +  Debe mantener un equilibrio en el uso de los recursos del sistema
-    \item Debe mantener un equilibrio en el uso de los recursos del sistema +
-\end{itemize}+
  
-\subsection{Programación concurrente, paralela y distribuida}+==== Programación concurrente, paralela y distribuida ====
  
-\subsubsection*{Programación concurrente}+=== Programación concurrente ===
  
 Es la programación de aplicaciones capaces de realizar varias tareas de forma simultánea utilizando hilos o threads. En este caso todas las tareas compiten por el uso del procesador (lo más habitual es disponer sólo de uno) y en un instante determinado sólo una de ellas se encuentra en ejecución. Además, habrá que tener en cuenta que diferentes hilos pueden compartir información entre sí y eso complica mucho su programación y coordinación. Es la programación de aplicaciones capaces de realizar varias tareas de forma simultánea utilizando hilos o threads. En este caso todas las tareas compiten por el uso del procesador (lo más habitual es disponer sólo de uno) y en un instante determinado sólo una de ellas se encuentra en ejecución. Además, habrá que tener en cuenta que diferentes hilos pueden compartir información entre sí y eso complica mucho su programación y coordinación.
  
-\subsubsection*{Programación paralela}+=== Programación paralela ===
  
 Es la programación de aplicaciones que ejecutan tareas de forma paralela, de forma que no compiten por el procesador puesto que cada una de ellas se ejecuta en uno diferente. Normalmente buscan resultados comunes dividiendo el problema en varias tareas que se ejecutan al mismo tiempo. Es la programación de aplicaciones que ejecutan tareas de forma paralela, de forma que no compiten por el procesador puesto que cada una de ellas se ejecuta en uno diferente. Normalmente buscan resultados comunes dividiendo el problema en varias tareas que se ejecutan al mismo tiempo.
  
-\subsubsection*{Programación distribuida}+=== Programación distribuida ===
  
 Es la programación de aplicaciones en las que las tareas a ejecutar se reparten entre varios equipos diferentes (conectados en red, a los que llamaremos nodos). Juntos, estos equipos, forman lo que se conoce como un Sistema Distribuido, que busca formar redes de equipos que trabajen con un fin común  Es la programación de aplicaciones en las que las tareas a ejecutar se reparten entre varios equipos diferentes (conectados en red, a los que llamaremos nodos). Juntos, estos equipos, forman lo que se conoce como un Sistema Distribuido, que busca formar redes de equipos que trabajen con un fin común 
  
-\begin{figure}[h!] +<figure> 
-    \centering +{concurrencia.jpg }
-    \includegraphics[scale=0.8]{partes/varios/concurrencia.jpg} +<caption>Concurrencia</caption></figure>
-    \caption{Programación concurrente/paralela} +
-\end{figure}+
  
-\begin{figure}[h!] +<figure> 
-    \centering +{distribuida.jpg }
-    \includegraphics[scale=1]{partes/varios/distribuida.jpg} +<caption>Programación distribuida</caption></figure>
-    \caption{Programación distribuida+
-\end{figure}+
  
-\subsection{¿Qué son los hilos?}+==== ¿Qué son los hilos? ====
  
 Un hilo o thread es cada una de las tareas que puede realizar de forma simultánea una aplicación. Por defecto, toda aplicación dispone de un único hilo de ejecución, al que se conoce como hilo principal. Si dicha aplicación no despliega ningún otro hilo, sólo será capaz de ejecutar una tarea al mismo tiempo en ese hilo principal. Un hilo o thread es cada una de las tareas que puede realizar de forma simultánea una aplicación. Por defecto, toda aplicación dispone de un único hilo de ejecución, al que se conoce como hilo principal. Si dicha aplicación no despliega ningún otro hilo, sólo será capaz de ejecutar una tarea al mismo tiempo en ese hilo principal.
Line 62: Line 52:
 En cualquier caso, es muy importante conocer los estados en los que se pueden encontrar un hilo. Estos estados se suelen representar mediante un gráfico como el que sigue: En cualquier caso, es muy importante conocer los estados en los que se pueden encontrar un hilo. Estos estados se suelen representar mediante un gráfico como el que sigue:
  
-\subsubsection*{Estados de un hilo}+=== Estados de un hilo === 
 +<figure> 
 +{{ estados_hilo.png }
 +<caption>Estados de un hilo</caption></figure>
  
-\begin{figure}[h!] +=== Programación multihilo en Java ===
-    \centering +
-    \includegraphics[scale=1.4]{partes/varios/estados_hilo.png} +
-    \caption{Estados de un hilo} +
-\end{figure}+
  
-\section{Programación multihilo en Java} +==== Creación de un hilo ====
- +
-\subsection{Creación de un hilo}+
  
 Para la creación de hilos en Java disponemos de varias vías, combinando el uso de la clase Thread y el interface Runnable según nos interese: Para la creación de hilos en Java disponemos de varias vías, combinando el uso de la clase Thread y el interface Runnable según nos interese:
  
-\begin{itemize} +  * Podemos utiliza la clase ''Thread'' heredando de ella. Es quizás la forma más cómoda porque una clase que hereda de Thread se convierte automáticamente en un hilo. Tiene una pega: esa clase ya no podrá heredera de ninguna otra, por lo que si la arquitectura de nuestra aplicación lo requiere ya no podríamos. 
-    \item Podemos utiliza la clase Thread heredando de ella. Es quizás la forma más cómoda porque una clase que hereda de Thread se convierte automáticamente en un hilo. Tiene una pega: esa clase ya no podrá heredera de ninguna otra, por lo que si la arquitectura de nuestra aplicación lo requiere ya no podríamos. +  Si tenemos la limitación que acabamos de comentar para el primer caso, podemos implementar el interface ''Runnable'' de forma que la clase que nosotros estamos implementado podrá además heredar sin ninguna limitación. Sólo cambia un poco la forma de trabajar directamente con la clase hilo. 
-    \item Si tenemos la limitación que acabamos de comentar para el primer caso, podemos implementar el interface Runnable de forma que la clase que nosotros estamos implementado podrá además heredar sin ninguna limitación. Sólo cambia un poco la forma de trabajar directamente con la clase hilo. +  Por otra parte también podemos crear un hilo utilizando una clase anónima. No es un método que se recomiende pero en algunos casos, cuando la clase que hace de hilo no va a tener una estructura concreta es bastante cómodo hacerlo de esta manera.
-    \item Por otra parte también podemos crear un hilo utilizando una clase anónima. No es un método que se recomiende pero en algunos casos, cuando la clase que hace de hilo no va a tener una estructura concreta es bastante cómodo hacerlo de esta manera. +
-\end{itemize}+
  
-\subsubsection*{Crear un hilo heredando de la clase Thread}+=== Crear un hilo heredando de la clase Thread ===
  
-\begin{lstlisting}[language=java]+<code java>
 public class Tarea extends Thread { public class Tarea extends Thread {
   @Override   @Override
Line 104: Line 89:
   }   }
 } }
-\end{lstlisting}+</code>
  
-\subsubsection*{Crear un hilo implementando la interfaz Runnable}+=== Crear un hilo implementando la interfaz Runnable ===
  
-\begin{lstlisting}[language=java]+<code java>
 public class OtraClase { public class OtraClase {
   . . .   . . .
Line 136: Line 121:
   }   }
 } }
-\end{lstlisting}+</code>
  
-\subsubsection*{Crear un hilo implementando una clase anónima/expresión lambda}+=== Crear un hilo implementando una clase anónima/expresión lambda ===
  
-\begin{lstlisting}[language=java]+<code java>
 public class Programa { public class Programa {
   public static void main(String args[]) {   public static void main(String args[]) {
Line 157: Line 142:
   }   }
 } }
-\end{lstlisting}+</code>
  
 En cualquier caso tenemos que tener siempre en cuenta las siguientes consideraciones: En cualquier caso tenemos que tener siempre en cuenta las siguientes consideraciones:
  
-\begin{itemize} +  * Siempre se debe sobreescribir (Override) el método ''run()'' e implementar allí lo que tiene que hacer el hilo 
-    \item Siempre se debe sobreescribir (Override) el método run() e implementar allí lo que tiene que hacer el hilo +  Podemos hacer que el hilo haga un número finito de cosas o bien que esté siempre en segundo plano (tendremos que asegurar que el método run() se ejecuta de forma continuada)(¿cómo se hace eso?) 
-    \item Podemos hacer que el hilo haga un número finito de cosas o bien que esté siempre en segundo plano (tendremos que asegurar que el método run() se ejecuta de forma continuada)(¿cómo se hace eso?) +  Los problemas vienen cuando existen varios hilos. Hay que tener en cuenta que pueden compartir datos y código y encontrarse en diferentes estados de ejecución 
-    \item Los problemas vienen cuando existen varios hilos. Hay que tener en cuenta que pueden compartir datos y código y encontrarse en diferentes estados de ejecución +  La ejecución de nuestra aplicación será thread-safe si se puede garantizar una correcta manipulación de los datos que comparten los hilos de la aplicación sin resultados inesperados (más adelante veremos cómo) 
-    \item La ejecución de nuestra aplicación será thread-safe si se puede garantizar una correcta manipulación de los datos que comparten los hilos de la aplicación sin resultados inesperados (más adelante veremos cómo) +  Además, en el caso de aplicaciones multihilo, también nos puede interesar sincronizar y comunicar unos hilos con otros
-    \item Además, en el caso de aplicaciones multihilo, también nos puede interesar sincronizar y comunicar unos hilos con otros +
-\end{itemize}+
  
-También resulta interesante saber cómo detener un hilo. En este caso, la API de Java desaconsejó el método stop() que en un principio ideó para detener la ejecución. Así, hoy en día, se nos anima a que seamos nosotros quienes implementemos formas limpias de detener nuestros hilos.+También resulta interesante saber cómo detener un hilo. En este caso, la API de Java desaconsejó el método ''stop()'' que en un principio ideó para detener la ejecución. Así, hoy en día, se nos anima a que seamos nosotros quienes implementemos formas limpias de detener nuestros hilos.
  
-\subsection{Sincronización de hilos}+==== Sincronización de hilos ====
  
-El API de Java proporciona una serie de métodos en la clase Thread para la sincronización de los hilos en una aplicación:+El API de Java proporciona una serie de métodos en la clase ''Thread'' para la sincronización de los hilos en una aplicación:
  
-\begin{itemize} +  - ''join()'' Se espera la terminación del hilo que invoca a este método antes de continuar 
-    \item \verb join()  Se espera la terminación del hilo que invoca a este método antes de continuar +  - ''Thread.sleep(int)'' El hilo que ejecuta esta llamada permanece dormido durante el tiempo especificado como parámetro (en ms) 
-    \item \verb Thread.sleep(int)  El hilo que ejecuta esta llamada permanece dormido durante el tiempo especificado como parámetro (en ms) +  - ''isAlive()'' Comprueba si el hilo permanece activo todavía (no ha terminado su ejecución) 
-    \item \verb isAlive()  Comprueba si el hilo permanece activo todavía (no ha terminado su ejecución) +  - ''yield()'' Sugiere al scheduler que sea otro hilo el que se ejecute (no se asegura)
-    \item \verb yield()  Sugiere al scheduler que sea otro hilo el que se ejecute (no se asegura) +
-\end{itemize}+
  
-\begin{lstlisting}[language=java]+<code java>
 public static void main(String args[]) { public static void main(String args[]) {
   Hilo hilo1 = new Thread(new Tarea());   Hilo hilo1 = new Thread(new Tarea());
Line 198: Line 179:
   System.out.println("Fin de la ejecución de los dos hilos");   System.out.println("Fin de la ejecución de los dos hilos");
 } }
-\end{lstlisting}+</code>
  
 El hilo principal espera a que ambos hilos se hayan ejecutado para continuar (o para lo que sea) El hilo principal espera a que ambos hilos se hayan ejecutado para continuar (o para lo que sea)
  
-\begin{lstlisting}[language=java]+<code java>
 public static void main(String args[]) { public static void main(String args[]) {
   Hilo hilo1 = new Thread(new Tarea());   Hilo hilo1 = new Thread(new Tarea());
Line 215: Line 196:
   System.out.println("Fin de la ejecución de los dos hilos");   System.out.println("Fin de la ejecución de los dos hilos");
 } }
-\end{lstlisting}+</code>
  
 En este caso los hilos se ejecutan uno después de otro En este caso los hilos se ejecutan uno después de otro
  
-\begin{lstlisting}[language=java]+<code java>
 public class Tarea implements Runnable { public class Tarea implements Runnable {
   @Override   @Override
Line 233: Line 214:
   }   }
 } }
-\end{lstlisting}+</code>
  
 En este caso el hilo duerme (detiene su ejecución) durante el tiempo especificado (en ms). Durante ese momento podrán ejecutarse otros hilos En este caso el hilo duerme (detiene su ejecución) durante el tiempo especificado (en ms). Durante ese momento podrán ejecutarse otros hilos
  
-\begin{lstlisting}[language=java]+<code java>
 public class TareaPrincipal implements Runnable { public class TareaPrincipal implements Runnable {
    
Line 293: Line 274:
   }   }
 } }
-\end{lstlisting}+</code> 
 + 
 +''isAlive()'' está indicando que el hilo está vivo (ha iniciado su ejecución y aún no ha muerto, puede estar en cualquier estado intermedio, incluso durmiendo) 
 + 
 +===== Programación concurrente ===== 
 + 
 +==== synchronize ==== 
 + 
 +  * En un entorno multi-hilo, se da una condición de carrera cuando más de un hilo intenta "al mismo tiempo" actualizar un recurso compartido (que puede ser una variable) 
 +  * Java proporciona un mecanismo para evitar estos problemas (palabra reserva ''synchronized''
 +  * Evita que más de un hilo puedan acceder a un zona de código o ejecutar un método determinado 
 + 
 +<code java> 
 +public class RecursoCompartido { 
 +  private int valor; 
 +   
 +  public synchronized void calcular() {  
 +    setValor(getValor() + 1); 
 +  } 
 +
 +</code> 
 + 
 +Sin la palabra reservada ''synchronized'', si más de un hilo ejecuta el método calcular, los resultados obtenidos pueden ser bastante inesperados (hacer prueba con y sin ella) 
 + 
 +==== Executors y pools de hilos ==== 
 + 
 +En el apartado anterior vimos como crear y lanzar hilos individualmente utilizando la clase ''Thread'' que Java proporcina en su API. El código funciona perfectamente pero realmente se vuelve complicado de desarrollar si lo que tenemos que gestionar es una gran cantidad de hilos de forma simultánea. Para esos casos, Java proporciona un framework, ''Executors'', que permite una gestión mucho más sencillo de 
 +cualquier tarea que se ejecuta en segundo plano. 
 + 
 +Utilizando ''Executors'', como programador, te olvidas en parte de la gestión de los hilos. Simplemente hay que centrarse en programar el código a realizar y asignar dicho código (una clase ''Runnable'' ) a un ''Executor'' , que se encargará de crear el hilo, lanzarlo y gestionar su ejecución. 
 + 
 +Veamos un ejemplo de cómo lanzar una serie de tareas en segundo plano utilizando la clase ''ExecutorService''  que viene con el framework ''Executors'' de Java. 
 + 
 +Supongamos una clase muy sencilla que implementa ''Runnable'' para definir un código que debe ejecutarse en segundo plano: 
 + 
 +<code java> 
 +public class Tarea implements Runnable { 
 +    @Override 
 +    public void run() { 
 +        System.out.println("Soy una clase que implementa Runnable"); 
 +        System.out.println("Ya me termino"); 
 +    } 
 +
 +</code> 
 + 
 +Para lanzar la tarea, o varias instancia de la misma, podemos crear un pool de hilos (en este caso dos) y pasarles las tareas al objeto ''Executor'' para que sea él quién los lance. En este caso, puesto que sólo se dispone de dos hilos en el pool, sólo se podrán ejecutar dos hilos en cada momento. Si en algún momento se ocuparán los dos hilos, las demás tareas tendrían que esperar hasta que alguno de los dos quedará libre para ejecutarse. 
 + 
 +Finalmente se ejecuta el método ''shutdown()'', que hará finalizar el ''Executor'' una vez hayan terminado todas las tareas que se encuentran ejecutándose con él. 
 + 
 +Hay que tener en cuenta que existe también un método ''shutdownNow'' que hace que el ''Executor'' finalice devolviendo todas las tareas que estaban esperando y no llegaron a ejecutarse. Se esperará que las tareas activas terminen o bien sean interrumpidas. 
 + 
 +<code java> 
 +// Crea un executor con dos hilos 
 +ExecutorService executor = Executors.newFixedThreadPool(2); 
 + 
 +// Encola tareas a ajecutar 
 +executor.execute(new Tarea()); 
 +executor.execute(new Tarea()); 
 +executor.execute(new Tarea()); 
 +executor.execute(new Tarea()); 
 + 
 +// Finaliza el executor. Terminará cuando todas las tareas que tiene 
 +// se acaben de forma natural 
 +executor.shutdown(); 
 +</code> 
 + 
 +También es posible trabajar con un ''ExecutorService'' que simplemente cuente con un hilo 
 + 
 +<code java> 
 +// Crea un executor con dos hilos 
 +ExecutorService executor = Executors.newSingleThreadExecutor(); 
 +executor.execute(new Tarea()); 
 +. . . 
 +</code> 
 + 
 +En el siguiente caso, instanciamos un pool de hilos planificados, de forma que podremos lanzar las tareas para que comiencen tras un tiempo de 
 +retraso asignado en su lanzamiento a través del método //schedule()//: 
 + 
 +<code java> 
 +// Crea un executor planificado con dos hilos 
 +ScheduledExecutorService executor = Executors.newScheduledThreadPool(2); 
 + 
 +// Encola tareas a ajecutar añadiendo retraso 
 +// en el lanzamiento de cada una de ellas 
 +executor.schedule(new Tarea(), 1, TimeUnit.SECONDS); 
 +executor.schedule(new Tarea(), 2, TimeUnit.SECONDS); 
 +executor.schedule(new Tarea(), 3, TimeUnit.SECONDS); 
 +executor.schedule(new Tarea(), 4, TimeUnit.SECONDS); 
 + 
 +// Finaliza el executor. Terminará cuando todas las tareas que tiene 
 +// se acaben de forma natural 
 +executor.shutdown(); 
 +</code> 
 + 
 +En cualquier caso, podemos encontrarnos con dos tipos de pools de hilos: 
 + 
 +  * **Fixed**: Se pueden crear a través de los métodos ''Executors.newFixedThreadPool(...)'' y crean un pool de hilos fijo (de número determinado) que se reutilizan a medida que van quedando libres. En ningún caso se crearán más hilos de los fijados al crear el pool. Si todos los hilos están ocupados y se encola una nueva tarea, ésta tendrá que esperar hasta que un hilo quede libre para ejecutarla. 
 +  * **Cached**: Se pueden crear a través de los métodos ''Executors.newCachedThreadPool(...)'' y crear un pool de hilos no fijo (sin número determinado) que se pueden reutilizar si quedan libres. En este caso, si todos los hilos están ocupados y se encola una nueva tarea se creará un nuevo hilo para poder ejecutarla. Los hilos que no hayan sido reutilizados en un tiempo determinado (6 segundos) serán finalizados y eliminados del pool. 
 + 
 +==== Callable y Future ==== 
 + 
 +Hasta el momento hemos trabajado con objetos ''Runnable'' como objetos que se pueden asignar a un hilo para ejecutar su código en segundo plano. Los objetos ''Runnable'' no son capaces de devolver ningún valor, simplemente ejecutan el código (el método ''run()'' no devuelve nada). Tampoco pueden lanzar excepciones puesto que el método ''run()'' de ''Runnable'' no está prearado para ello. 
 +Java proporciona también una interface llamada ''Callable'' que permite crear objetos que pueden ser asignados a hilos para ejecutarse en segundo plano, y que además permite que éstas tareas devuelvan un resultado al finalizar y que lancen una excepción. 
 + 
 +<code java> 
 +public class TareaCallable implements Callable<Boolean>
 +    @Override 
 +    public Boolean call() throws Exception { 
 +        // Realiza algo 
 +        return true; 
 +    } 
 +
 +</code> 
 + 
 +Ligado a este nuevo tipo de tarea ''Callable'' aparece un nuevo tipo llamado ''Future'' que es el tipo de dato en el que estas nuevas tareas devolverán su valor. Puesto que ahora tenemos una tarea en segundo plano que debe devolver un valor al finalizar, necesitamos, de alguna manera, esperar y recoger ese valor cuando corresponda. Así, un objeto ''Future'' es capaz de almacenar el valor de devolución de un método que ha sido lanzado pero todavía no ha terminado. 
 + 
 +Veamos ahora un ejemplo lanzando una tarea ''Callable'' y cómo se recoge su resultado: 
 + 
 +<code java> 
 +// Crea un executor con dos hilos 
 +ExecutorService executor = Executors.newFixedThreadPool(2); 
 + 
 +// Los Callable se ejecutan con el método submit() 
 +Future<Boolean> futuro = executor.submit(new TareaCallable()); 
 + 
 +// Podemos seguir ejecutando código en el hilo principal 
 +System.out.println("El programa sigue funcionando . . ."); 
 + 
 +// Recogemos el resultado de la tarea Callable 
 +// Hay que tener en cuenta que la llamada al método get() 
 +// bloquea la ejecución hasta que termine la tarea 
 +Boolean resultado = futuro.get(); 
 +System.out.println("El resultado de la tarea es: " + resultado); 
 + 
 +// Finaliza el executor. Terminará cuando todas las tareas que tiene 
 +// se acaben de forma natural 
 +executor.shutdown(); 
 +</code> 
 + 
 +Hay que tener en cuenta que la llamada al método ''get()'' bloquea la ejecución del hilo principal hasta que la tarea termine y devuelva el resultado. La clave aqui está en que unas líneas antes ya tenemos el objeto ''Future'' y podemos seguir ejecutando instrucciones hasta que realmente necesitemos utilizar el valor, que será cuando invoquemos al método ''get()''
 + 
 +Además, siempre podremos cancelar el objeto ''Future'' e incluso comprobar si ha terminado para decidir hacer algo mientras la tarea no lo haya hecho. De esa manera, una vez lanzada la tarea y obtenido el futuro, podemos ver si procede o no acabar obteniendo el valor que devuelva. En el siguiente caso, vamos a suponer que a partir de un cierto tiempo de ejecución se cancela la tarea y el programa sigue. Modificamos la tarea ''TareaCallable'' para forzar a que dure un tiempo determinado y que asi el ejemplo tenga cierta duración y podamos comprobar como espera (o no) en función del tiempo de //timeout// que le hayamos asignado, o bien utilizando otro criterio cualquiera. 
 + 
 +<code java> 
 +public class TareaCallable implements Callable<Boolean>
 +    @Override 
 +    public Boolean call() throws Exception { 
 +        // Realiza algo 
 +        return true; 
 +    } 
 +
 +</code> 
 + 
 +Y el programa principal quedaría asi: 
 + 
 +<code java> 
 +// Crea un executor con dos hilos 
 +ExecutorService executor = Executors.newSingleThreadExecutor(); 
 + 
 +long tiempoPasado = 0; 
 +long tiempoInicio = System.currentTimeMillis(); 
 +// Los Callable se ejecutan con el método submit() 
 +Future<Boolean> futuro = executor.submit(new TareaCallable()); 
 + 
 +// Podemos seguir ejecutando código en el hilo principal 
 +System.out.println("El programa sigue funcionando . . ."); 
 + 
 +// Ejecutamos mientras la tarea no haya terminado 
 +while (!futuro.isDone()) { 
 +    try { 
 +        System.out.println("Esperando a que termine la tarea o haciendo algo mientras"); 
 +        Thread.sleep(300); 
 +        // Vamos sumando el tiempo de espera 
 +        tiempoPasado = System.currentTimeMillis() - tiempoInicio; 
 + 
 +        // 4000 hace de timeout. Pasado ese tiempo cancelamos la tarea 
 +        // del segundo plano 
 +        if (tiempoPasado > 4000) { 
 +            futuro.cancel(true); 
 +        } 
 +    } catch (InterruptedException ie) { 
 +        ie.printStackTrace(); 
 +    } 
 +
 + 
 +// Llegados a este punto podemos saber si la tarea fue cancelada 
 +if (futuro.isCancelled()) { 
 +    System.out.println("La tarea fue cancelada"); 
 +
 +// o bien si terminó correctamente 
 +else { 
 +    // Recogemos el resultado de la tarea Callable 
 +    try { 
 +        Boolean resultado = futuro.get(); 
 +        System.out.println("El resultado de la tarea es: " + resultado); 
 +    } catch (ExecutionException|InterruptedException e) { 
 +        e.printStackTrace(); 
 +    } 
 +
 + 
 +// Finaliza el executor. Terminará cuando todas las tareas que tiene 
 +// se acaben de forma natural 
 +executor.shutdown(); 
 +</code> 
 + 
 +En cualquier caso, el método ''get()'' puede ser invocado para establecer un tiempo de //timeout//, a partir del cual el método devolvería una excepción ''TimeoutException''. Es un caso muy útil cuando, desde nuestra aplicación, invocamos a algún servicio remoto del que no podemos garantizar su disponibilidad en todo momento. 
 + 
 +<code java> 
 +. . . 
 +// Espera 10 segundos antes de lanzar la excepción TimeoutException 
 +Boolean resultado = futuro.get(10, TimeUnit.SECONDS); 
 +. . . 
 +</code> 
 + 
 +Además, es posible lanzar varias tareas al mismo tiempo con el método ''executeAll'' de la clase ''ExecutorService'': 
 + 
 +<code java> 
 +. . . 
 +List<Callable<Boolean>> listaTareas = . . . 
 +List<Future<Boolean>> futuros = excecutor.invokeAll(listaTareas); 
 +. . . 
 +for (Future<Boolean> futuro : futuros) { 
 +    System.out.println("Resultado :" + futuro.get()); 
 +
 +. . . 
 +</code> 
 + 
 +==== CompletableFuture ==== 
 + 
 +Proporciona una API más completa que la que hay para la clase ''Future''
 + 
 +  * Lanzar una tarea en segundo plano de forma asíncrona y ejecutar un método al finalizar ésta (a través de la llamada a un método callback) 
 + 
 +<code java> 
 +CompletableFuture.runAsync(() -> doSomething()) 
 +    .whenComplete((string, throwable) -> doSomethingWhenFinishFuture()); 
 +</code> 
 + 
 +  * Lanzar una tarea y ejecutar algo cuando ésta termina: 
 + 
 +<code java> 
 +private String doSomethingAndReturnResult() { 
 +    return "ok"; 
 +
 + 
 +CompletableFuture.supplyAsync(() -> doSomethingAndReturnResult()) 
 +                .thenAccept(System.out::println) 
 +                .whenComplete((nothing, error) -> System.out.println("Fin")); 
 +</code> 
 + 
 + 
 +==== CountDownLatch ==== 
 + 
 +Sirve como contador para bloquear una llamada hasta que éste llega a cero 
 + 
 +Se puede utilizar para que el código del hilo principal no continúe hasta que algo se haya ejecutado por todos los hilos que son necesarios 
 + 
 +<code java> 
 +public class Tarea implements Runnable { 
 +  private CountDownLatch countDownLatch; 
 +   
 +  public Tarea(CountDownLatch countDownLatch) {  
 +    this.countDownLatch = countDownLatch; 
 +  } 
 + 
 +  @Override 
 +  public void run() { 
 +    hacerAlgo(); 
 +    countDownLatch.countDown();  
 +  } 
 +}  
 +   
 +. . . 
 +. . . 
 + 
 +CountDownLatch countDownLatch = new CountDownLatch(4);  
 +for . . . 
 +  executorService.execute(new Tarea(countDownLatch)); 
 +. . . 
 +countDownLatch.await(); // Espera hasta que el contador sea 0 System.out.println("Ya se han ejecutado las 5 tareas"); 
 +</code> 
 + 
 +==== CyclicBarrier ==== 
 + 
 +Similar a ''CountDownLatch'' pero reutilizable 
 + 
 +Se utiliza para que los hilos se esperen los unos a los otros antes de realizar alguna tarea concreta 
 + 
 +<code java> 
 +public class Tarea implements Runnable { 
 +  private CyclicBarrier cyclicBarrier; 
 +   
 +  public Tarea(CyclicBarrier cyclicBarrier) {  
 +    this.cyclicBarrier = cyclicBarrier; 
 +  } 
 + 
 +  @Override 
 +  public void run() { 
 +    hacerAlgo();  
 +    cyclicBarrier.await();  
 +    hacerOtraCosa(); 
 +  }  
 +
 + 
 +. . .  
 + 
 +// Acepta como segundo argumento un Runnable que será ejecutado por el último hilo que pase la barrera 
 +CyclicBarrier cyclicBarrier = new CyclicBarrier(2);  
 +if (!cyclicBarrier.isBroken()) { 
 +  executorService.execute(new Tarea(cyclicBarrier));  
 +  . . . 
 +
 +</code> 
 + 
 +==== Colecciones sincronizadas ==== 
 + 
 +Son lo que se conocen como colecciones //thread-safe//, puesto que están listas para usarse en entornos de concurrencia con varios hilos ejecutándose. 
 + 
 +En la clase ''Collection'' hay una serie de métodos estáticos que devuelven instancia de colecciones sincronizadas. Por ejemplo: 
 + 
 +  * synchronizedList() 
 +  * synchronizedMap() 
 +  * synchronizedSet() 
 + 
 +Además, existe alguna colección ya sincronizada como ''Vector'' (similar a ''ArrayList'' y ''HashTable'' (similar a ''HashMap''
 + 
 +==== Objetos atómicos ==== 
 + 
 +Los objetos atómicos son estructuras Java que sirven para representar valores y realizar operaciones de forma atómica para algunos tipos de datos. 
 + 
 +''AtomicInt'', ''AtomicLong// y ''AtomicBoolean'' son algunos de los objetos atómicos que tenemos disponibles dentro de la API de Java 
 + 
 +Por poner algún ejemplo, ''AtomicInt'' garantiza la ejecución atómica de algunas operaciones como estas: 
 +  * ''get() 
 +  * ''compareAndSet(valorComparacion, nuevoValor)'' 
 +  * ''addAndGet(valor)'' 
 +  * ''getAndIncrement()'' 
 +  * ''incrementAndGet()'' 
 +  * ''decrementAndGet()'' 
 +  * ''getAndDecrement()'' 
 + 
 + 
 +==== Variables volatile ==== 
 + 
 +Es una palabra reservada que sirve para indicarle a Java que la variable debe ser escriba en memoria principal de forma inmediata cuando vea modificado su valor, para evitar que otros hilos que la utilicen puedan tener un valor diferente no actualizado 
 + 
 +  * private volatile int valor; 
 + 
 +Hay que tener en cuenta que no siempre será suficiente y sólo servirá para situaciones donde un hilo esté leyendo alguna variable que otro hilo modifique. No nos ayudará a evitar las condiciones de carrera que hemos visto anteriormente. En esos casos necesitaremos, como ya vimos, crear zonas sincronizadas para evitar problemas. 
 + 
 +<figure> 
 +{{ volatile.png }} 
 +<caption>variable volatile</caption></figure> 
 + 
 + 
 +==== Streams paralelos ==== 
 + 
 +La ejecución en paralelo significa que se ejecutan fragmentos de código de una aplicación al mismo tiempo. 
 + 
 +Hay que tener en cuenta que, en determinadas circunstancias, puede no interesar puesto que, al ejecutarse de forma paralela, no se garantiza en que orden se ejecutarán 
 + 
 +En otras circustancias puede interesar puesto que la aplicación aprovecha mucho más los recursos de la máquina al forzar a ésta a ejecutar algunas operaciones de forma simultánea 
 + 
 +<figure> 
 +{{ parallel-sequential.png }} 
 +<caption>Ejecución stream secuencial vs stream paralelo</caption></figure> 
 + 
 +En los streams paralelos, se procesan los elementos de forma paralela por segmentos 
 + 
 +<code java> 
 +List<String> cities = new ArrayList<>();  
 +// TODO Poblar la lista de ciudades  
 +cities.parallelStream() 
 +  .forEach(System.out::println); 
 +</code>  
 + 
 +Puede mejorar el rendimiento en algunos casos, ya que permite que la aplicación realice algunos cálculos (matemáticos en este caso) de forma paralela, aprovechando mucho más los recursos de la máquina 
 + 
 +<code java> 
 +long count = Stream.iterate(0, i -> i + 1) 
 +  .limit(500000) 
 +  .parallel()  
 +  .filter(Main::isPrime)  
 +  .count(); 
 +System.out.println("Hay " + count + " números primos"); 
 +</code> 
 + 
 +==== Locks ==== 
 + 
 +Es otro mecanismo para la sincronización de hilos parecido a como hace ''synchronized'' 
 + 
 +Es algo más flexible que ''synchronized'', puesto que podemos bloquear/desbloquear la zona exclusiva en diferentes métodos  
 + 
 +''Lock'' es un interface que tiene varias opciones para implementar. Veremos un ejemplo con ''ReentrantLock'' 
 + 
 +<code java> 
 +Lock lock = new ReentrantLock();  
 +lock.lock(); 
 +try { 
 +  // Aqui se accede al recurso/código compartido 
 +} finally {  
 +  lock.unlock(); 
 +
 +</code> 
 + 
 +Podemos encapsular la operación de bloqueo (lock) para evitar que se produzcan deadlocks (esperar indefinidamente un recurso) utilizando el método ''tryLock'' indicando el tiempo que el hilo esperará por el recurso 
 + 
 +<code java> 
 +boolean isLocked = lock.tryLock(2, TimeUnit.SECONDS);  
 +if (isLocked) { 
 +  try { 
 +  // Aqui se accede al recurso/código compartido 
 +  } finally {  
 +    lock.unlock(); 
 +  }  
 +
 +</code> 
 +==== Patrón Observer ==== 
 + 
 +Patrón de diseño por el cual un objeto, llamada sujeto, mantiene a otros objetos, llamados observadores, notificados acerca de cualquier cambio que sobre él se produce. 
 + 
 +Hasta Java 9, se proporcionaba una serie de clases que permitían definir una solución para este Patrón. A partir de entonces se recomienda el uso de ''PropertyChangeListener'' para dicha implementación. 
 + 
 +=== Clase Observable === 
 + 
 +<code java> 
 +public class Product { 
 +  private String nombre; 
 +  . . . 
 +  private PropertyChangeSupport change; 
 +   
 +  @Override 
 +  public void addPropertyChangeListener(PropertyChangeListener listener) { 
 +    change.addPropertyChangeListener(listener);  
 +  } 
 +   
 +  @Override 
 +  public void removePropertyChangeListener(PropertyChangeListener listener) { 
 +    change.removePropertyChangeListener(listener);  
 +  } 
 +   
 +  public void decreaseStock(int quantity) {  
 +    change.firePropertyChange("stock", stock, (stock – quantity)); 
 +    stock -= quantity; 
 +  }  
 +
 +</code> 
 + 
 +=== La clase Observer === 
 + 
 +<code java> 
 +public class Provider implements PropertyChangeListener {  
 +  private String name; 
 +  . . . 
 +  @Override 
 +  public void propertyChange(PropertyChangeEvent event) { 
 +    if (event.getPropertyName().equals("stock")) {  
 +      System.out.println("El stock ha bajado"); 
 +    }  
 +  } 
 +
 +</code> 
 + 
 +Y el programa principal para ver el ejemplo funcionar: 
 + 
 +<code java> 
 +public static void main(String args[]) {  
 +  Product product = new Product(); 
 +  . . . 
 +  Customer customer = new Customer(); 
 +  . . .  
 +  product.addPropertyChangeListener(customer);  
 +  product.setStock(10); // ¿Qué ocurre aqui? .. . 
 +} 
 +</code>
  
-\verb isAlive()  está indicando que el hilo está vivo (ha iniciado su ejecución y aún no ha muerto, puede estar en cualquier estado intermedio, incluso durmiendo)+----
  
 +(c) 2021-{{date>%Y}} Santiago Faci
  
-=====  
apuntes/concurrencia.1615382334.txt.gz · Last modified: 2021/03/10 13:18 by Santiago Faci