apuntes:concurrencia
Differences
This shows you the differences between two versions of the page.
Next revisionBoth sides next revision | |||
apuntes:concurrencia [2021/03/10 13:18] – created Santiago Faci | apuntes:concurrencia [2021/03/10 13:19] – Santiago Faci | ||
---|---|---|---|
Line 298: | Line 298: | ||
- | ===== | + | ===== Programación concurrente ===== |
+ | |||
+ | \part*{Programación concurrente} | ||
+ | |||
+ | \section{Executors y pools de hilos} | ||
+ | |||
+ | En el apartado anterior vimos como crear y lanzar hilos individualmente utilizando la clase \verb Thread | ||
+ | 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, \emph{Executors}, | ||
+ | cualquier tarea que se ejecuta en segundo plano. | ||
+ | |||
+ | Utilizando \emph{Executors}, | ||
+ | el código a realizar y asignar dicho código (una clase \verb Runnable ) a un \verb 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 \verb ExecutorService | ||
+ | \emph{Executors} de Java. | ||
+ | |||
+ | Supongamos una clase muy sencilla que implementa \verb Runnable | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | public class Tarea implements Runnable { | ||
+ | @Override | ||
+ | public void run() { | ||
+ | System.out.println(" | ||
+ | System.out.println(" | ||
+ | } | ||
+ | } | ||
+ | \end{lstlisting} | ||
+ | |||
+ | 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 | ||
+ | \verb Executor | ||
+ | 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 \verb shutdown() , que hará finalizar el \emph{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 \verb shutdownNow() | ||
+ | tareas que estaban esperando y no llegaron a ejecutarse. Se esperará que las tareas activas terminen o bien sean interrumpidas. | ||
+ | |||
+ | \begin{lstlisting}[language=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(); | ||
+ | \end{lstlisting} | ||
+ | |||
+ | También es posible trabajar con un \verb ExecutorService | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | // Crea un executor con dos hilos | ||
+ | ExecutorService executor = Executors.newSingleThreadExecutor(); | ||
+ | executor.execute(new Tarea()); | ||
+ | . . . | ||
+ | \end{lstlisting} | ||
+ | |||
+ | En el siguiente caso, instanciamos un pool de hilos planificados, | ||
+ | retraso asignado en su lanzamiento a través del método \verb schedule() : | ||
+ | |||
+ | \begin{lstlisting}[language=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(); | ||
+ | \end{lstlisting} | ||
+ | |||
+ | En cualquier caso, podemos encontrarnos con dos tipos de pools de hilos: | ||
+ | |||
+ | \begin{itemize} | ||
+ | \item \textbf{Fixed} Se pueden crear a través de los métodos \verb Executors.newFixedThreadPool(...) | ||
+ | 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. | ||
+ | \item \textbf{Cached} Se pueden crear a través de los métodos \verb Executors.newCachedThreadPool(...) | ||
+ | (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. | ||
+ | \end{itemize} | ||
+ | |||
+ | \section{Callable y Future} | ||
+ | |||
+ | Hasta el momento hemos trabajado con objetos \verb Runnable | ||
+ | plano. Los objetos \verb Runnable | ||
+ | devuelve nada). Tampoco pueden lanzar excepciones puesto que el método \verb run() de \verb Runnable | ||
+ | |||
+ | Java proporciona también una interface llamada \verb Callable | ||
+ | segundo plano, y que además permite que éstas tareas devuelvan un resultado al finalizar y que lancen una excepción. | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | public class TareaCallable implements Callable< | ||
+ | @Override | ||
+ | public Boolean call() throws Exception { | ||
+ | // Realiza algo | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | \end{lstlisting} | ||
+ | |||
+ | Ligado a este nuevo tipo de tarea \verb Callable | ||
+ | tareas devolverán su valor. Puesto que ahora tenemos una tarea en segundo plano que debe devolver un valor al finalizar, necesitamos, | ||
+ | alguna manera, esperar y recoger ese valor cuando corresponda. Así, un objeto \verb Future | ||
+ | un método que ha sido lanzado pero todavía no ha terminado. | ||
+ | |||
+ | Veamos ahora un ejemplo lanzando una tarea \verb Callable | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | // Crea un executor con dos hilos | ||
+ | ExecutorService executor = Executors.newFixedThreadPool(2); | ||
+ | |||
+ | // Los Callable se ejecutan con el método submit() | ||
+ | Future< | ||
+ | |||
+ | // Podemos seguir ejecutando código en el hilo principal | ||
+ | System.out.println(" | ||
+ | |||
+ | // 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(" | ||
+ | |||
+ | // Finaliza el executor. Terminará cuando todas las tareas que tiene | ||
+ | // se acaben de forma natural | ||
+ | executor.shutdown(); | ||
+ | \end{lstlisting} | ||
+ | |||
+ | Hay que tener en cuenta que la llamada al método \verb 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 \verb Future | ||
+ | que realmente necesitemos utilizar el valor, que será cuando invoquemos al método \verb get() . | ||
+ | |||
+ | Además, siempre podremos cancelar el objeto \verb Future | ||
+ | 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 \verb TareaCallable | ||
+ | comprobar como espera (o no) en función del tiempo de //timeout// que le hayamos asignado, o bien utilizando otro criterio cualquiera. | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | public class TareaCallable implements Callable< | ||
+ | @Override | ||
+ | public Boolean call() throws Exception { | ||
+ | // Realiza algo | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | \end{lstlisting} | ||
+ | |||
+ | Y el programa principal quedaría asi: | ||
+ | |||
+ | \begin{lstlisting}[language=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< | ||
+ | |||
+ | // Podemos seguir ejecutando código en el hilo principal | ||
+ | System.out.println(" | ||
+ | |||
+ | // Ejecutamos mientras la tarea no haya terminado | ||
+ | while (!futuro.isDone()) { | ||
+ | try { | ||
+ | System.out.println(" | ||
+ | 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(" | ||
+ | } | ||
+ | // o bien si terminó correctamente | ||
+ | else { | ||
+ | // Recogemos el resultado de la tarea Callable | ||
+ | try { | ||
+ | Boolean resultado = futuro.get(); | ||
+ | System.out.println(" | ||
+ | } catch (ExecutionException|InterruptedException e) { | ||
+ | e.printStackTrace(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Finaliza el executor. Terminará cuando todas las tareas que tiene | ||
+ | // se acaben de forma natural | ||
+ | executor.shutdown(); | ||
+ | \end{lstlisting} | ||
+ | |||
+ | En cualquier caso, el método \verb get() puede ser invocado para establecer un tiempo de \emph{timeout}, | ||
+ | devolvería una excepción \verb TimeoutException . Es un caso muy útil cuando, desde nuestra aplicación, | ||
+ | del que no podemos garantizar su disponibilidad en todo momento. | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | . . . | ||
+ | // Espera 10 segundos antes de lanzar la excepción TimeoutException | ||
+ | Boolean resultado = futuro.get(10, | ||
+ | . . . | ||
+ | \end{lstlisting} | ||
+ | |||
+ | Además, es posible lanzar varias tareas al mismo tiempo con el método \verb executeAll | ||
+ | |||
+ | \begin{lstlisting}[language=java] | ||
+ | . . . | ||
+ | List< | ||
+ | List< | ||
+ | . . . | ||
+ | for (Future< | ||
+ | System.out.println(" | ||
+ | } | ||
+ | . . . | ||
+ | \end{lstlisting} | ||
+ | |||
+ | \section{Sincronización de hilos} | ||
+ | |||
+ | \subsection{Monitores} | ||
+ | |||
+ | \subsection{Semáforos} | ||
+ | |||
+ | \subsection{CountDownLatch} | ||
+ | |||
+ | \subsection{CyclicBarrier} | ||
+ | |||
+ | \section{Colecciones sincronizadas} | ||
+ | |||
+ | \section{Objetos atómicos} | ||
+ | |||
+ | \section{Objetos volatile} | ||
+ | |||
+ | \section{Streams paralelos} | ||
+ | |||
+ | \section{Patrón Observer} |
apuntes/concurrencia.txt · Last modified: 2023/05/28 23:59 by Santiago Faci