apuntes:concurrencia
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
apuntes:concurrencia [2023/05/28 11:02] – [Colecciones sincronizadas] Santiago Faci | apuntes:concurrencia [2023/05/28 23:59] (current) – Santiago Faci | ||
---|---|---|---|
Line 36: | Line 36: | ||
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, | 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, | ||
- | \begin{figure}[h!] | + | <figure> |
- | | + | {{ concurrencia.jpg |
- | \includegraphics[scale=0.8]{partes/ | + | <caption> |
- | \caption{Programación concurrente/paralela} | + | |
- | \end{figure} | + | |
- | \begin{figure}[h!] | + | <figure> |
- | \centering | + | {{ distribuida.jpg |
- | | + | <caption>Programación distribuida</ |
- | \caption{Programación distribuida} | + | |
- | \end{figure} | + | |
==== ¿Qué son los hilos? ==== | ==== ¿Qué son los hilos? ==== | ||
Line 57: | Line 53: | ||
=== Estados de un hilo === | === Estados de un hilo === | ||
- | + | <figure> | |
- | \begin{figure}[h!] | + | {{ estados_hilo.png |
- | | + | <caption>Estados de un hilo</ |
- | \includegraphics[scale=1.4]{partes/ | + | |
- | \caption{Estados de un hilo} | + | |
- | \end{figure} | + | |
=== Programación multihilo en Java === | === Programación multihilo en Java === | ||
Line 70: | Line 63: | ||
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: | ||
- | * 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. | + | * Podemos utiliza la clase '' |
- | * 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. | + | * Si tenemos la limitación que acabamos de comentar para el primer caso, podemos implementar el interface |
* 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. | * 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. | ||
Line 153: | Line 146: | ||
En cualquier caso tenemos que tener siempre en cuenta las siguientes consideraciones: | En cualquier caso tenemos que tener siempre en cuenta las siguientes consideraciones: | ||
- | * Siempre se debe sobreescribir (Override) el método run() e implementar allí lo que tiene que hacer el hilo | + | * Siempre se debe sobreescribir (Override) el método |
* 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?) | * 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 | * 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 | ||
Line 159: | Line 152: | ||
* Además, en el caso de aplicaciones multihilo, también nos puede interesar sincronizar y comunicar unos hilos con otros | * Además, en el caso de aplicaciones multihilo, también nos puede interesar sincronizar y comunicar unos hilos con otros | ||
- | 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 |
==== 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 '' |
- | - //join()// | + | - '' |
- | - //Thread.sleep(int)// | + | - '' |
- | - isAlive()// | + | - '' |
- | - yield()// | + | - '' |
<code java> | <code java> | ||
Line 283: | Line 276: | ||
</ | </ | ||
- | //isAlive()// | + | '' |
===== Programación concurrente ===== | ===== Programación concurrente ===== | ||
Line 290: | Line 283: | ||
* En un entorno multi-hilo, se da una condición de carrera cuando más de un hilo intenta "al mismo tiempo" | * En un entorno multi-hilo, se da una condición de carrera cuando más de un hilo intenta "al mismo tiempo" | ||
- | * Java proporciona un mecanismo para evitar estos problemas (palabra reserva | + | * Java proporciona un mecanismo para evitar estos problemas (palabra reserva |
* Evita que más de un hilo puedan acceder a un zona de código o ejecutar un método determinado | * Evita que más de un hilo puedan acceder a un zona de código o ejecutar un método determinado | ||
Line 303: | Line 296: | ||
</ | </ | ||
- | Sin la palabra reservada | + | Sin la palabra reservada |
==== Executors y pools de hilos ==== | ==== 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 | + | En el apartado anterior vimos como crear y lanzar hilos individualmente utilizando la clase '' |
- | 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, | + | |
cualquier tarea que se ejecuta en segundo plano. | cualquier tarea que se ejecuta en segundo plano. | ||
- | Utilizando | + | Utilizando |
- | 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 \verb ExecutorService | + | Veamos un ejemplo de cómo lanzar una serie de tareas en segundo plano utilizando la clase '' |
- | //Executors// de Java. | + | |
- | Supongamos una clase muy sencilla que implementa | + | Supongamos una clase muy sencilla que implementa |
<code java> | <code java> | ||
Line 330: | Line 319: | ||
</ | </ | ||
- | 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 | + | 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// | + | |
- | 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 | + | Finalmente se ejecuta el método |
- | encuentran ejecutándose con él. | + | |
- | Hay que tener en cuenta que existe también un método | + | Hay que tener en cuenta que existe también un método |
- | tareas que estaban esperando y no llegaron a ejecutarse. Se esperará que las tareas activas terminen o bien sean interrumpidas. | + | |
<code java> | <code java> | ||
Line 356: | Line 340: | ||
</ | </ | ||
- | También es posible trabajar con un //ExecutorService// que simplemente cuente con un hilo | + | También es posible trabajar con un '' |
<code java> | <code java> | ||
Line 386: | Line 370: | ||
En cualquier caso, podemos encontrarnos con dos tipos de pools de hilos: | En cualquier caso, podemos encontrarnos con dos tipos de pools de hilos: | ||
- | * **Fixed**: Se pueden crear a través de los métodos | + | * **Fixed**: Se pueden crear a través de los métodos |
- | * **Cached**: Se pueden crear a través de los métodos | + | * **Cached**: Se pueden crear a través de los métodos |
==== Callable y Future ==== | ==== Callable y Future ==== | ||
- | Hasta el momento hemos trabajado con objetos | + | Hasta el momento hemos trabajado con objetos |
- | Java proporciona también una interface llamada | + | Java proporciona también una interface llamada |
<code java> | <code java> | ||
Line 404: | Line 388: | ||
</ | </ | ||
- | Ligado a este nuevo tipo de tarea //Callable// aparece un nuevo tipo llamado | + | Ligado a este nuevo tipo de tarea '' |
- | Veamos ahora un ejemplo lanzando una tarea //Callable// y cómo se recoge su resultado: | + | Veamos ahora un ejemplo lanzando una tarea '' |
<code java> | <code java> | ||
Line 429: | Line 413: | ||
</ | </ | ||
- | Hay que tener en cuenta que la llamada al método | + | Hay que tener en cuenta que la llamada al método |
- | Además, siempre podremos cancelar el objeto | + | |
+ | Además, siempre podremos cancelar el objeto | ||
<code java> | <code java> | ||
Line 494: | Line 479: | ||
</ | </ | ||
- | En cualquier caso, el método | + | En cualquier caso, el método |
<code java> | <code java> | ||
Line 503: | Line 488: | ||
</ | </ | ||
- | Además, es posible lanzar varias tareas al mismo tiempo con el método | + | Además, es posible lanzar varias tareas al mismo tiempo con el método |
<code java> | <code java> | ||
Line 515: | Line 500: | ||
. . . | . . . | ||
</ | </ | ||
+ | |||
+ | ==== CompletableFuture ==== | ||
+ | |||
+ | Proporciona una API más completa que la que hay para la clase '' | ||
+ | |||
+ | * 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, | ||
+ | </ | ||
+ | |||
+ | * Lanzar una tarea y ejecutar algo cuando ésta termina: | ||
+ | |||
+ | <code java> | ||
+ | private String doSomethingAndReturnResult() { | ||
+ | return " | ||
+ | } | ||
+ | |||
+ | CompletableFuture.supplyAsync(() -> doSomethingAndReturnResult()) | ||
+ | .thenAccept(System.out:: | ||
+ | .whenComplete((nothing, | ||
+ | </ | ||
+ | |||
==== CountDownLatch ==== | ==== CountDownLatch ==== | ||
Line 549: | Line 558: | ||
==== CyclicBarrier ==== | ==== CyclicBarrier ==== | ||
- | Similar a CountDownLatch pero reutilizable | + | Similar a '' |
Se utiliza para que los hilos se esperen los unos a los otros antes de realizar alguna tarea concreta | Se utiliza para que los hilos se esperen los unos a los otros antes de realizar alguna tarea concreta | ||
Line 578: | Line 587: | ||
} | } | ||
</ | </ | ||
+ | |||
==== Colecciones sincronizadas ==== | ==== Colecciones sincronizadas ==== | ||
- | Son lo que se conocen como colecciones thread-safe, | + | Son lo que se conocen como colecciones |
+ | |||
+ | En la clase '' | ||
- | En la clase Collection hay una serie de métodos estáticos que devuelven instancia de colecciones sincronizadas. Por ejemplo: | ||
* synchronizedList() | * synchronizedList() | ||
* synchronizedMap() | * synchronizedMap() | ||
* synchronizedSet() | * synchronizedSet() | ||
- | Además, existe alguna colección ya sincronizada como //Vector// (similar a //ArrayList//) y //HashTable// (similar a //HashMap//) | + | Además, existe alguna colección ya sincronizada como '' |
==== Objetos atómicos ==== | ==== Objetos atómicos ==== | ||
- | ==== Objetos | + | Los objetos atómicos son estructuras Java que sirven para representar valores y realizar operaciones de forma atómica para algunos tipos de datos. |
+ | |||
+ | '' | ||
+ | |||
+ | Por poner algún ejemplo, '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | |||
+ | |||
+ | ==== Variables | ||
+ | |||
+ | 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, | ||
+ | |||
+ | < | ||
+ | {{ volatile.png }} | ||
+ | < | ||
==== Streams paralelos ==== | ==== 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, | ||
+ | |||
+ | 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 | ||
+ | |||
+ | < | ||
+ | {{ parallel-sequential.png }} | ||
+ | < | ||
+ | |||
+ | En los streams paralelos, se procesan los elementos de forma paralela por segmentos | ||
+ | |||
+ | <code java> | ||
+ | List< | ||
+ | // TODO Poblar la lista de ciudades | ||
+ | cities.parallelStream() | ||
+ | .forEach(System.out:: | ||
+ | </ | ||
+ | |||
+ | 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, | ||
+ | .limit(500000) | ||
+ | .parallel() | ||
+ | .filter(Main:: | ||
+ | .count(); | ||
+ | System.out.println(" | ||
+ | </ | ||
==== Locks ==== | ==== Locks ==== | ||
+ | Es otro mecanismo para la sincronización de hilos parecido a como hace '' | ||
+ | |||
+ | Es algo más flexible que '' | ||
+ | |||
+ | '' | ||
+ | |||
+ | <code java> | ||
+ | Lock lock = new ReentrantLock(); | ||
+ | lock.lock(); | ||
+ | try { | ||
+ | // Aqui se accede al recurso/ | ||
+ | } finally { | ||
+ | lock.unlock(); | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Podemos encapsular la operación de bloqueo (lock) para evitar que se produzcan deadlocks (esperar indefinidamente un recurso) utilizando el método '' | ||
+ | |||
+ | <code java> | ||
+ | boolean isLocked = lock.tryLock(2, | ||
+ | if (isLocked) { | ||
+ | try { | ||
+ | // Aqui se accede al recurso/ | ||
+ | } finally { | ||
+ | lock.unlock(); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
==== Patrón Observer ==== | ==== Patrón Observer ==== | ||
+ | |||
+ | Patrón de diseño por el cual un objeto, llamada sujeto, mantiene a otros objetos, llamados observadores, | ||
+ | |||
+ | 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 '' | ||
+ | |||
+ | === 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 -= quantity; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | === La clase Observer === | ||
+ | |||
+ | <code java> | ||
+ | public class Provider implements PropertyChangeListener { | ||
+ | private String name; | ||
+ | . . . | ||
+ | @Override | ||
+ | public void propertyChange(PropertyChangeEvent event) { | ||
+ | if (event.getPropertyName().equals(" | ||
+ | System.out.println(" | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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); | ||
+ | } | ||
+ | </ | ||
---- | ---- |
apuntes/concurrencia.1685271766.txt.gz · Last modified: 2023/05/28 11:02 by Santiago Faci