Logo Passei Direto

URJC _ Grado en Ingeniería de Computadores _ asignatura_ Programacion Concurrente en Java _ Academia_z

User badge image
Diego Pereira

en

Material

Esta es una vista previa del archivo. Inicie sesión para ver el archivo original

Academia/.DS_Store
__MACOSX/Academia/._.DS_Store
Academia/Practica2/Enunciado Práctica 2 20_21 v2.pdf
Enunciado Práctica Evaluable 2 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
1 
 
Enunciado Práctica Evaluable 2 
Almacén de pedidos online 
 
Objetivo 
Que el alumno ponga en práctica los conocimientos teóricos y prácticos adquiridos en la Parte II 
(Tema 5 en adelante) de la asignatura de Programación Concurrente. 
Normativa, fecha y mecanismo de entrega 
 
La práctica podrá realizarse de forma individual o por parejas. 
 
La fecha límite para la entrega de la práctica será el día indicado en la actividad correspondiente en 
el Aula Virtual, a través del cual se realizará la entrega de la práctica. Se deberá entregar un .zip cuyo 
contenido será el proyecto Eclipse en el que se ha realizado la práctica. El nombre del fichero .zip 
tiene que ser igual al identificador del alumno en la URJC (la parte antes de la @ del correo 
electrónico del alumno en la URJC). En caso de que la práctica haya sido realizada por dos alumnos, 
se incluirán ambos nombres separados por “-” y ambos alumnos deben entregar la práctica de manera 
independiente. 
 
Además del código fuente, se deberá elaborar una breve memoria explicativa del mismo en formato 
PDF. La memoria deberá guardarse en la carpeta src/main/resources y tendrá el nombre 
“memoria.pdf”. Deberá incluir como mínimo, las sincronizaciones y las estructuras de datos usadas. 
 
Los alumnos que hayan cursado esta asignatura el curso anterior y que hubieran aprobado la práctica 
2 en dicho curso no tendrán que volver a realizarla. Esta práctica será convalidada por defecto con la 
práctica 2 del curso anterior, asignando la nota que se obtuviera. No obstante, si el alumno desea 
realizar la práctica nuevamente, puede hacerla y entregarla en forma y fecha. 
Enunciado 
Una tienda de muebles a medida, llamado Ukea, decide simular en un programa informático 
concurrente la interacción que se produce en cada una de sus tiendas. Esta simulación les permitirá 
analizar de forma más precisa su funcionamiento y podrán optimizar sus procesos. 
Se modelarán diversos tipos de personas: 
• Cliente: Representa a los clientes que van a realizar pedidos en una tienda. 
• Empleado: Representa a todos los tipos de empleados de Ukea. Esta categoría se divide en 
las siguientes: 
o EmpCogePedidos: Empleados encargados de atender a los clientes para tomarles el 
pedido y, cuando ha sido dispensado, cobrarles. 
o EmpElaboraProductos: Existen algunos productos especiales que necesitan una 
preparación previa, ya que se componen a su vez de varios productos. Los empleados 
encargados de elaborar productos se encargan de esta labor. 
Enunciado Práctica Evaluable 2 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
2 
 
o EmpPreparaPedidos: Empleados encargados de entregar los pedidos a los clientes 
con todos los productos que han pedido. Lo hacen en un dispensador de pedidos. 
o EmpEncargado: Encargado de la tienda, que supervisa el funcionamiento de la 
misma. 
 
La tienda recibe pedidos las 14 horas que está abierta, no obstante, los trabajadores solo trabajan en 
dos turnos de 7 horas cada uno. 
A continuación, se describe el ciclo de vida de cada uno de los procesos: 
• Cliente: Un cliente podrá hacer un pedido en cualquier instante del horario de apertura. 
Cuando no hace un pedido realizará su vida normal (se supone que hace compras cada cierto 
tiempo aleatorio). 
• Empleados: Todos los empleados realizarán su vida normal y luego irán al trabajo. 
Empezarán a trabajar cuando el encargado se los indique y acabaran cuando el encargado le 
diga que tienen que dejar de trabajar. En ese momento se marcharán de la tienda. Realizarán 
su vida cotidiana y volverán a trabajar cuando el encargado les avise. 
• EmpCogePedidos: Este empleado atenderá pedidos de los clientes. Recogerá un pedido (cada 
vez) y comprobará que hay existencias en el almacén. Si es así (hay unidades de todos los 
productos pedidos) informará a los EmpElaboraProductos para que comiencen con la 
elaboración de los productos especiales (si los hay) y a los EmpPreparaPedido para que 
realicen su tarea (preparar el pedido y dárselo al cliente). Cuando el pedido ha sido puesto en 
el dispensador, avisará al cliente y procederá a cobrar el importe del pedido. 
• EmpElaboraProductos: Este empleado estará a la espera de que le pidan que elabore un 
producto especial. Cuando reciba la petición, elaborará el producto pedido (retirando los 
productos del almacén) y lo dejará en una zona intermedia para que sean recogido por el 
EmpPreparaPedido. 
• EmpPreparaPedido: Este empleado se encarga de recoger los productos del almacén que no 
necesitan una preparación especial y de esperar a que los EmpElaboraProductos elaboren los 
productos más complejos. Cuando el pedido está completo le entrega por un dispensador el 
pedido al cliente. Si no hay dispensadores libres, deberá esperar. Cuando haya dejado el 
pedido en el dispensador, avisará al EmpcogePedidos para que avise al cliente que su pedido 
está preparado y listo para recoger. 
• EmpEncargado: Este empleado se encargará de abrir el almacén y la tienda por la mañana y 
de cerrarlos por la noche. Cuando la tienda está abierta al público, revisará a intervalos 
regulares el trabajo de los empleados y el comportamiento de los clientes. Entre otras cosas, 
revisará el número de dispensadores libres y el tamaño de la cola de clientes. 
Para implementar el programa se ofrecen un conjunto de clases que forma la arquitectura de la 
aplicación. La clase principal es la clase Personal, que contiene el ciclo de vida de todos los procesos. 
Las clases auxiliares Pedido, Producto, ProductoEspecial se utilizan en la implementación de la 
clase Personal. La clase Ukea es la encargada de realizar todas las sincronizaciones y 
comunicaciones entre los hilos del programa que representan personas. 
A continuación, se muestra el código fuente de Personal: 
 
 
Enunciado Práctica Evaluable 2 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
3 
 
package ukea; 
 
 
public class Personal { 
 
 private static final int NUM_CLIENTES = 8; 
 private static final int NUM_EMP_COGE_PEDIDOS = 4; 
 private static final int NUM_EMP_ELABORA_PRODUCTOS = 3; 
 private static final int NUM_EMP_PREPARA_PEDIDO = 3; 
 private static final int NUM_DISPENSADORES = 2; 
 
 private static Ukea ukea = new Ukea(NUM_DISPENSADORES); 
 
 // ---------------- Métodos procesos ----------------- 
 
 public static void empCogePedidos() { 
 …. 
 } 
 
 public static void empElaboraProductos() { 
 …. 
 } 
 
 public static void empPreparaPedido() { 
 … 
 } 
 
 public static void cliente() { 
 … 
 } 
 
 public static void encargado() { 
 
 } 
 // ------------------------- Main ------------------------- 
 public static void main(String[] args) { 
 //Creación de hilos que realizan llamadas a los métodos correspondientes 
 } 
} 
Enunciado Práctica Evaluable 2 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
4 
 
 
Se pide 
Se pide implementar completamente en Java un programa concurrente que simule el funcionamiento 
de una tienda Ukea. Para ello se deberá implementar completamente la clase Ukea y Personal, así 
como el resto de clase auxiliares. Además, si lo estimas conveniente, el código que se proporciona 
con este enunciado podrá ser modificado para cumplir con ciertos requisitos del enunciado. 
Para la implementación se deberán tener en cuenta las siguientes consideraciones: 
• Cualquier decisión respecto al enunciado, no especificada en el mismo, debe explicarse ade-
cuadamente en la memoria. 
• Deberán utilizarse las clases de la API de Java más adecuadas para implementar la sincroni-
zación y comunicación de la clase Ukea. Queda excluido el uso de la biblioteca SimpleConcu-
rrent (empleada en la primera parte de la asignatura). 
• El número de empleados de cada tipo debe poder configurarse fácilmente. Por ejemplo, me-
diante una serie de constantes. 
• Cuando el encargado notifica a sus empleados el fin de la jornada, podrá usar interrupciones. 
• El número de pedido es un contador que se iniciará en 1 cada vez que se ejecute el pro-
grama y se incrementa por cada pedido. 
• El horizonte temporal en el que llegan los pedidos y la velocidad de llegada de los pedidos 
debe poder configurarse también mediante algún parámetro. 
• Se recomienda asignar un factor rectificación de tiempo, de modo que se pueda simular una 
ejecución de 24 horas en unos cuantos segundos. 
• Toda la información de lo que está sucediendo debe mostrarse por pantalla. Por ejemplo, 
cuando un determinado cliente hace un pedido, se mostrará los datos del cliente y del pedido. 
También debe mostrarse cuando el pedido es recogido, cuando se está elaborando, cuando está 
depositado en un determinado dispensador, etc. 
 
• NOTA: Para realizar la simulación, se supondrá lo siguiente: 
• Cada producto se identifica por un valor entero único y tiene un precio. 
• En el almacén hay 10 productos (identificados del 0 al 9) y 15 unidades de cada pro-
ducto. 
• Los pedidos de cada cliente se generan de forma aleatoria. Como mucho, compra 5 
productos en cada pedido. Los productos especiales se generan con un número de dos 
cifras, que significa que están compuestos por una unidad de cada producto asociado 
al identificador. Es decir, si el cliente pide un producto especial con el número 23, eso 
significa que está compuesto por un producto con id= 2 y con un producto con id=3. 
• Los clientes tienen 300 euros para gastar. Cuando no pueden pagar el pedido, se anula. 
• Si un pedido de un cliente no puede servirse (porque el cliente no pueda pagar o porque 
no haya suficientes unidades en el almacén) el producto se anula. 
 
		Objetivo
		Normativa, fecha y mecanismo de entrega
		Enunciado
		Se pide
__MACOSX/Academia/Practica2/._Enunciado Práctica 2 20_21 v2.pdf
Academia/Tema4/Tema 4 - Concurrencia en los lenguajes.pdf
Programación
Concurrente
Tema 3
Concurrencia en 
los lenguajes de 
programación
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
2
Concurrencia en los Lenguajes de Programación
• Introducción
• Concurrencia en Java
• Concurrencia en C/C++
• Concurrencia en JavaScript
• Conclusiones
PROGRAMACIÓN CONCURRENTE
3
Introducción
• Para implementar un programa informático 
deben seleccionarse la plataforma de 
desarrollo:
 Sistema Operativo: Windows XP, Ubuntu, Windows 
7, Mac OS X, iOS, Android, RedHat, …
 Lenguaje de programación: C, C++, Java, Python, 
COBOL, ActionScript, JavaScript, Ruby…
 Librerías: Las librerías disponibles para cada 
lenguaje en cada Sistema Operativo
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
4
Sistema Operativo
• Hay muchos tipos de sistemas operativos:
 Familias: Windows (WinXP, Win7), Unix (linux, 
MacOS, Android, iOS) y otros tipos como Solaris
 Arquitecturas HW: 32bits vs 64bits (x86 vs 
AMD64), RISC vs CISC (x86 vs ARM).
 Uso (Empotrados, PC Escritorio, Servidores): iOS, 
Android, Windows 7, Ubuntu, Solaris, HPUX, AIX…
INTRODUCCIÓN
5
Lenguaje de programación
• Hay muchos lenguajes de programación
• Se diferencian entre sí por…:
 Sistema de tipos estático vs dinámico
 Compilado vs Interpretado vs Máquina Virtual
 Portables vs Centrado en una Arquitectura
 Orientados a sistemas vs Lenguajes de script
 Imperativo vs Declarativo
 Imperativo: Orientado a Objetos vs Estructurado
 Declarativo: Funcional vs Lógico
INTRODUCCIÓN
6
Lenguaje de programación
• Ejemplos
 Java: Tipos estáticos, portable, orientado a objetos, 
Máquina virtual
 C/C++: Tipos estáticos, compilado, sistemas, 
imperativo, estructurado
 JavaScript: Tipos dinámicos, portable, estructurado 
y orientado a objetos, interpretado y máquina 
virtual
INTRODUCCIÓN
7
Librerías
• Las librerías* son muy importantes para el 
desarrollo de aplicaciones
 Librerías del sistema operativo: win32 (windows), 
POSIX (linux), librerías Android, librerías iOS…
 Librerías de la plataforma de desarrollo: Java, 
JavaScript (navegador web), Python…
 Librerías de terceros: Librerías comerciales o libres, 
librerías portables entre sistemas operativos, librerías 
genéricas vs específicas 
INTRODUCCIÓN
Deberían llamarse bibliotecas al ser una traducción de “library”
8
Plataforma de desarrollo
• Para desarrollar una aplicación hay que tener 
en cuenta la plataforma de desarrollo
 Lenguaje de programación
 Sistema Operativo
 Librerías
• La programación concurrente está muy 
influenciada por la plataforma de desarrollo 
elegida
INTRODUCCIÓN
9
Plataformas de desarrollo
• A continuación se realizará una presentación 
básica de la programación concurrente en las 
siguientes plataformas de desarrollo:
• Esto permite al alumno tener una visión 
general de las diferentes formas de utilizar la 
programación concurrente al desarrollar una 
aplicación
INTRODUCCIÓN
Java C/C++ JavaScript
10
Concurrencia en los Lenguajes de Programación
• Introducción
• Concurrencia en Java
• Concurrencia en C/C++
• Concurrencia en JavaScript
• Conclusiones
PROGRAMACIÓN CONCURRENTE
11
Concurrencia en Java
• Java es una plataforma de desarrollo formada 
por 
 Lenguaje de programación Java
 Máquina virtual de Java (JVM)
 Librería estándar (API)
• La máquina virtual y la librería estándar ofrece 
portabilidad en sistemas operativos y 
plataformas hardware
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
12
Concurrencia en Java
•Modelo de concurrencia
 Java ofrece un modelo de concurrencia de memoria 
compartida integrado en el lenguaje Java y en la 
librería estándar
 El soporte de concurrencia ha evolucionado mucho 
en Java
 En las últimas versiones aparecen herramientas de 
alto nivel en la librería estándar que permiten al 
desarrollador abstraerse de detalles de bajo nivel
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
13
Concurrencia en Java
• La concurrencia también está integrada en el 
propio lenguaje de programación
 Está definido cómo se comparte la memoria entre 
diferentes hilos en el Modelo de Memoria de Java 
[1] (Java Memory Model)
 Existen palabras reservadas en el propio lenguaje 
para delimitar zonas bajo exclusión mutua 
(synchronized) y para especificar que un atributo es 
compartido entre hilos (volatile)
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
[1] http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html 
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html
14
Concurrencia en Java
• También tiene soporte en la librería estándar
 Como Java es un lenguaje orientado a objetos, los 
hilos se representan como objetos de la clase 
java.lang.Thread
 Esta clase tiene métodos para controlar el ciclo de 
vida del hilo (configurar su nombre, prioridad, iniciar, 
esperar a que finalice, etc…)
 En Java los hilos se pueden crear y destruir en 
cualquier momento de la ejecución del programa, no 
tienen que seguir la rígida estructura de los programas 
de SimpleConcurrent
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
15
package ejemplo.gestionhilo;
public class Programa {
 public static void main(String[] args) {
 new Thread(()->{
 System.out.println("Soy un hilo");
 }).start();
 }
} Comienza la ejecución del hilo, el método start() no 
se espera a que finalice la ejecución del hilo.
El código del hilo se codifica en 
una expresión lambda
Concurrencia en Java
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
16
Concurrencia en Java
• En java se pueden usar otros modelos de concurrencia 
con librerías
 Actores: 
 Akka: http://akka.io/
 Quasar:
http://www.paralleluniverse.co/quasar/ 
 Memoria software transaccional: 
 Multiverse: http://multiverse.codehaus.org
 Comunicando procesos secuenciales (CSP): 
 JCSP: http://www.cs.kent.ac.uk/projects/ofa/jcsp/
 Programación funcional: 
 Framework Collections en Java 8: 
http://java.dzone.com/articles/title-devoxx-2012-java-8
 RxJava: https://github.com/Netflix/RxJava
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://akka.io/
http://akka.io/
http://www.paralleluniverse.co/quasar/
http://www.paralleluniverse.co/quasar/
http://multiverse.codehaus.org/
http://www.cs.kent.ac.uk/projects/ofa/jcsp/
http://java.dzone.com/articles/title-devoxx-2012-java-8
https://github.com/Netflix/RxJava
17
Concurrencia en Java
• En la mayoría de lenguajes de programación 
imperativos se usan las mismas herramientas 
presentes en Java
• La plataforma Java se estudiará en detalle en el 
Tema 5 – Programación Concurrente en Java
• Se desarrollarán programas concurrentes 
reales utilizando herramientas profesionales
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
18
Concurrencia en los Lenguajes de Programación
• Introducción
• Concurrencia en Java
• Concurrencia en C/C++
• Concurrencia en JavaScript
• Conclusiones
PROGRAMACIÓN CONCURRENTE
19
Concurrencia en C/C++
• C/C++ es una familia de lenguajes de 
programación de bajo nivel
• Están diseñados para el acceso al hardware y 
el rendimiento
• Esto hace que se usen especialmente en:
 Programación de sistemas
 Sistemas empotrados con recursos limitados
 Computación de altas prestaciones
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
20
Concurrencia en C/C++
• Existen diversos estándares de C/C++
 C90, C99, C++03, C++11, C++14
• Existen muchos compiladores de C/C++ con diferentes 
soporte de los estándares
 gcc: http://gcc.gnu.org/ 
 clang: http://clang.llvm.org/
 VisualC++: http://www.microsoft.com/visualstudio/
 Intel C/C++ Compilers: 
http://software.intel.com/en-us/c-compilers/
 Hay muchos más… (http://en.wikipedia.org/wiki/C99)
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://gcc.gnu.org/
http://gcc.gnu.org/
http://clang.llvm.org/
http://www.microsoft.com/visualstudio/
http://software.intel.com/en-us/c-compilers/
http://en.wikipedia.org/wiki/C99
21
Concurrencia en C/C++
• Existen dos enfoques para programación 
concurrente en C/C++
 Concurrencia en el lenguaje: Extensiones del 
lenguaje no estándar que “paralelizan” el código 
automáticamente para que algunas partes sean 
ejecutadas en paralelo
 Concurrencia con librerías: Librerías no estándar 
para el desarrollo de programas concurrentes
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
22
Concurrencia en C/C++
• Concurrencia en el lenguaje
 Existen extensiones de algunos compiladores
 Amplían C/C++ con nuevas palabras reservadas
 Usando esas palabras reservadas se transforma el 
código para que sea ejecutado en diferentes hilos 
de ejecución
 Normalmente transforman bucles de secuencial a 
paralelo
 Más importantes: OpenMP e Intel Cilk Plus
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
23
Concurrencia en C/C++
• OpenMP
 Disponible en los compiladores Intel C/C++, gcc, …
 Permite ejecutar concurrentemente bucles, 
bloques, etc.
 Trabaja con un esquema fork / join para que un hilo 
principal reparta el trabajo en hilos y espere al 
resultado de todos ellos
 http://openmp.org/
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://openmp.org/
24
Concurrencia en C/C++
• Ejemplo OpenMP
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
double ProductoPunto(double* a, double* b, int size)
{
 double c = 0;
 #pragma omp parallel for reduction(+:c)
 for (int i = 0; i < size; ++i)
 {
 c += a[i]*b[i];
 }
 return c;
}
Con este comando se permite paralelizar 
la ejecución de las iteraciones del buble y 
luego “reducir” los valores de cada hilo 
con una operación de suma
25
Concurrencia en C/C++
• Intel Cilk Plus
 Disponible en los compiladores Intel C/C++ y gcc
 Usando sólo tres nuevas palabras clave se pueden 
paralelizar operaciones sobre arrays
 http://www.cilkplus.org/ 
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://www.cilkplus.org/
26
Concurrencia en C/C++
• Ejemplo Intel Cilk Plus
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
int fib(int n) 
{ 
 if (n < 2) 
 return n; 
 int x = cilk_spawn fib(n-1); 
 int y = fib(n-2); 
 cilk_sync; 
 return x + y; 
}
Con este comando se indica que fib(n-1) 
se puede ejecutar en un nuevo hilo en 
paralelo con el hilo principal que 
ejecutará fib(n-2)
Con este comando se indica que 
el hilo se bloquea hasta que han 
terminado los otros hilos
27
Concurrencia en C/C++
• Concurrencia con Librerías
 La librería estándar de C++ era muy básica, y en la 
práctica cada sistema operativo ofrecía su propio 
conjunto de librerías
 Win32: Librería de la familia de sistemas operativos 
windows (95, 98, XP, 2000, Vista, 8..)
 POSIX, GTK, KDE…: Librerías de la familia de 
sistemas operativos UNIX / Linux (RedHay, 
Ubuntu…)
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
28
Concurrencia en C/C++
• Win32 y POSIX permiten la programación 
concurrente en un modelo de memoria compartida 
 Cada librería con tipos y funciones (API) diferentes
 Win32
 API win32 ofrece las primitivas de concurrencia.
 http://msdn.microsoft.com/en-us/library/ms686937(v=vs.85).aspx
 Otras librerías concurrentes para Visual C++: 
http://archive.msdn.microsoft.com/concrtextras
 POSIX 
 Podemos referirnos a ellos como Native POSIX Threads Library 
(NPTL) o pthreads
 https://computing.llnl.gov/tutorials/pthreads/
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://msdn.microsoft.com/en-us/library/ms686937(v=vs.85).aspx
http://archive.msdn.microsoft.com/concrtextras
https://computing.llnl.gov/tutorials/pthreads/
29
Concurrencia en C/C++
• Existen algunas librerías multiplataforma para 
programación concurrente con memoria compartida 
que se pueden usar en Unix, Mac y Windows 
 Boost
 También tiene otro tipo de funcionalidades
 http://www.boost.org/doc/libs/1_43_0/doc/html/thread.html
 Intel Threading Building Blocks 
 También dispone de herramientas de alto nivel (estructuras de 
datos, gestión de hilos, …)
 http://threadingbuildingblocks.org/
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://www.boost.org/doc/libs/1_43_0/doc/html/thread.html
http://threadingbuildingblocks.org/
30
Ejemplo 
POSIX pthreads
Tema 4 - Concurrencia en los Lenguajes de Programación 30
void *thr_func(void *arg) {
 printf("hello from new thread\n"); 
 pthread_exit(NULL);
}
 
int main(int argc, char **argv) {
 pthread_t thr[NUM_THREADS];
 int i, rc;
 for (i = 0; i < NUM_THREADS; ++i) {
 rc = pthread_create
 (&thr[i], NULL, thr_func, NULL);
 if (rc) {
 fprintf(stderr, "error rc: %d\n", rc);
 return -1;
 }
 }
 
 for (i = 0; i < NUM_THREADS; ++i) {
 pthread_join(thr[i], NULL);
 }
 
 return 0;
}
Código que se ejecuta en el 
hilo de forma concurrente
Creación de los hilos e 
inicio de la ejecución
Bloqueo hasta que 
terminan los hilos
31
Ejemplo 
win32 Threads
Tema 4 - Concurrencia en los Lenguajes de Programación 31
DWORD WINAPI thr_func(LPVOID args) {
 printf("hello from new thread\n"); 
 return 0;
}
 
int main(int argc, char **argv) {
 DWORD thrIds[NUM_THREADS];
 HANDLE thr[NUM_THREADS];
 int i, rc;
 for (i = 0; i < NUM_THREADS; ++i) {
 thr[i] = CreateThread
 (0,0, thr_func,0,0,&thrIds[i]);
 if (!thr[i]) {
 fprintf(stderr, "error in threads\n”);
 return -1;
 }
 }
 
 for (i = 0; i < NUM_THREADS; ++i) {
 WaitForSingleObject(thr[i], INFINITE);
 CloseHandle(thr[i]);
 } 
 return 0;
}
Código que se ejecuta en el 
hilo de forma concurrente
Creación de los hilos e 
inicio de la ejecución
Bloqueo hasta que 
terminan los hilos
32
Concurrencia en C/C++
• C++11
 Define una forma estándar de crear hilos
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
#include <thread> 
#include <iostream> 
int main(){ 
 std::thread t1([](){ 
 std::cout << "Hello from thread " << std::endl; 
 });
 t1.join(); 
 return 0; 
}
https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/ 
https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/
https://www.classes.cs.uchicago.edu/archive/2013/spring/12300-1/labs/lab6/
33
Concurrencia en C/C++
• Conclusiones
 Las plataformas mayoritarias tienen librerías para 
la programación concurrente de bajo nivel con 
memoria compartida (win32, POSIX…)
 El estándar C++11 ya proporciona una forma 
estándar de gestionar hilos
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
34
Concurrencia en C/C++
• Conclusiones
 Existen librerías multiplataforma que 
proporcionan herramientas de más alto nivel 
(Boost, Intel TBB…)
 Se pueden usar variantes del lenguaje C/C++ que 
ofrecen herramientas del alto nivel especialmente 
diseñadas para paralelización de algoritmos 
(OpenMP, Intel Cilk Plus…)
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
35
Concurrencia en los Lenguajes de Programación
• Introducción
• Concurrencia en Java
• Concurrencia en C/C++
• Concurrencia en JavaScript
• Conclusiones
PROGRAMACIÓN CONCURRENTE
36
Concurrencia en JavaScript
• JavaScript es un lenguaje que se diseñó para ejecutarse en 
el contexto de una página web dentro de un navegador 
web
• Los navegadores actuales ejecutan el código JavaScript 
con máquinas virtuales* (V8 de Chrome, IonMonkey en 
Firefox…)
• JavaScript es el nombre “popular” del estándar 
ECMAScript
• Muchos de los aspectos de la plataforma de desarrollo con 
JavaScript se definen bajo el estándar HTML5
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
* http://en.wikipedia.org/wiki/JavaScript_engine
http://en.wikipedia.org/wiki/JavaScript_engine
37
Concurrencia en JavaScript
• El estándar WebWorkers dentro de HTML5 permite 
usar un modelo de programación concurrente de 
paso de mensajes en JavaScript
• A los hilos en segundo plano se les denomina 
WebWorkers (o simplemente workers)
• Los workers no usan memoria compartida, sólo se 
comunican mediante mensajes entre hilos
• Un worker se puede iniciar desde el script principal 
(main) o desde otro worker
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
38
Concurrencia en JavaScript
• En JavaScript el código siempre se ejecuta en respuesta a 
un evento (carga de página, evento del usuario, 
temporizador, llamada AJAX…)
• Un worker funciona de forma similar, sólo ejecuta código 
ante la llegada de un mensaje desde el script principal o 
desde otro worker
• Un worker también puede enviar mensajes de vuelta
• Tutorial: 
http://anders.janmyr.com/2013/02/web-workers.html
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
http://anders.janmyr.com/2013/02/web-workers.html
39
Ejemplo 
WebWorkers
self.addEventListener('message', function(e) 
{
 //do somethin useful
 self.postMessage(e.data);
}, false);
Código del hilo 
(WebWorker)
var worker = new Worker('doWork.js');
worker.addEventListener('message', 
 function(e) {
 console.log('Worker said: ', e.data);
 }, false);
// Send data to the worker.
worker.postMessage('Hello World'); 
Script principal
doWork.js
Ejecución del hilo 
(WebWorker) al enviarle un 
mensaje
Código de respuesta al 
recibir un mensaje del 
WebWorker
Concurrencia en JavaScript
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
40
Concurrencia en JavaScript
• También existen extensiones (no estándar) del 
lenguaje JavaScript para concurrencia
 Intel RiverTrail 
 Enfocado a la implementación de algoritmos 
paralelos
 Disponible como plugin de Firefox
 Quizás se estandarice en el futuro
 https://github.com/RiverTrail/RiverTrail/wiki
 http://www.infoq.com/news/2011/11/webgl-webcl-multi
core-rivertrail
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
https://github.com/RiverTrail/RiverTrail/wiki
https://github.com/RiverTrail/RiverTrail/wiki
http://www.infoq.com/news/2011/11/webgl-webcl-multicore-rivertrail
http://www.infoq.com/news/2011/11/webgl-webcl-multicore-rivertrail
41
Concurrencia en JavaScript
• Actualmente se está popularizado el uso de JavaScript 
fuera del navegador 
• El más popular es el servidor web node.js (basado en V8 
de Chrome) que utiliza librerías (módulos)
• Módulos para WebWorkers en node.js
 https://npmjs.org/package/webworker-threads
• Módulos para la gestión directa de procesos en node.js
 http://nodejs.org/api/child_process.html
 https://github.com/passcod/DynWorker
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
https://npmjs.org/package/webworker-threads
http://nodejs.org/api/child_process.html
https://github.com/passcod/DynWorker
42
Concurrencia en los Lenguajes de Programación
• Introducción
• Concurrencia en Java
• Concurrencia en C/C++
• Concurrencia en JavaScript
• Conclusiones
PROGRAMACIÓN CONCURRENTE
43
Conclusiones
• Cada plataforma de desarrollo (lenguaje, sistema 
operativo y librerías) tiene sus propios mecanismos de 
programación concurrente
• Las plataformas que soportan el modelo de memoria 
compartida:
 Suelen ofrecer herramientas de bajo nivel similares: 
 Gestión de hilos y herramientas de sincronización
 Habitualmente disponen de librerías de alto nivel:
 Estructuras de datos concurrentes, herramientas avanzadas de 
sincronización, otros modelos de programación concurrente…)
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
44
Conclusiones
• Existen plataformas muy extendidas que 
tienen un soporte de concurrencia limitado o 
únicamente ofrecen modelos de alto nivel 
(JavaScript, Go, ActionScript/Flash…)
• Esto se debe a que la programación 
concurrente es compleja y se ha restringido 
en ámbitos en los que se considera que no es 
imprescindible o se puede ofrecer un modelo 
más sencillo
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
45
Conclusiones
• La programación concurrente no es una 
disciplina nueva en informática
• Pero la llegada de los procesadores multicore 
ha obligado a las plataformas de desarrollo a 
ofrecer herramientas para aprovechar (y no 
desperdiciar) todo esa potencia de cómputo
• En los próximos años evolucionarán y se 
popularizarán las herramientas de 
concurrencia que ofrecen las plataformas
CONCURRENCIA EN LOS LENGUAJES DE PROGRAMACIÓN
		Slide 1
		Concurrencia en los Lenguajes de Programación
		Introducción
		Sistema Operativo
		Lenguaje de programación
		Lenguaje de programación
		Librerías
		Plataforma de desarrollo
		Plataformas de desarrollo
		Concurrencia en los Lenguajes de Programación
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en Java
		Concurrencia en los Lenguajes de Programación
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Ejemplo POSIX pthreads
		Ejemplo win32 Threads
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en C/C++
		Concurrencia en los Lenguajes de Programación
		Concurrencia en JavaScript
		Concurrencia en JavaScript
		Concurrencia en JavaScript
		Ejemplo WebWorkers
		Concurrencia en JavaScript
		Concurrencia en JavaScript
		Concurrencia en los Lenguajes de Programación
		Conclusiones
		Conclusiones
		Conclusiones
__MACOSX/Academia/Tema4/._Tema 4 - Concurrencia en los lenguajes.pdf
Academia/Tema3/Tema3 - Concurrencia con paso de mensajes.pdf
Programación
Concurrente
Tema 3
Paso de
Mensajes
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
2
Concurrencia con Paso de Mensajes
● Introducción
● Identificación de procesos
● Sincronización
● Canal de comunicación
● Conclusiones
PROGRAMACIÓN CONCURRENTE
3
● Las técnicas usadas para la construcción de 
programas concurrentes se dividen en dos 
grandes modelos:
● Memoria compartida: Los procesos pueden 
acceder a una memoria compartida para 
comunicarse y sincronizarse.
● Paso de mensajes: Los procesos sólo se 
pueden comunicar y sincronizar con el envío 
de mensajes de uno a otro.
Introducción
CONCURRENCIA CON PASO DE MENSAJES
4
● ¿Cuándo usar paso de mensajes?
● Tiene que usarse obligatoriamente en sistemas 
multiprocesador poco acoplados (sistemas 
distribuidos)
● Puede usarse también en sistemas muy acoplados:
● El programa será más escalable (podrá ser usado en un 
cluster en el futuro)
● Cuando el modelo de concurrencia sea más adecuado 
para el programa concreto
● Cuestión de gustos.
Introducción
CONCURRENCIA CON PASO DE MENSAJES
5
Introducción
•Modelos híbridos
 Es habitual que un sistema informático distribuido 
combine ambos modelos.
 Modelo de paso de mensajes para comunicación 
entre nodos.
 Modelo de memoria compartida para comunicación 
entre procesos dentro del mismo nodo.
 Pero cada vez se usan más modelos puros basado 
en paso de mensajes en ambos niveles
CONCURRENCIA CON PASO DE MENSAJES
6
• Primitivas básicas en un modelo de paso de 
mensajes:
 Send: Envía información (mensaje) a otro proceso
 Receive: Recibe información (mensaje) de otro 
proceso
Introducción
CONCURRENCIA CON PASO DE MENSAJES
Emisor ReceptorCanal
Mensaje
7
• Estas primitivas pueden ser:
 Explícitas: Cuando se usan directamente por el 
programador
 Implícitas: Cuando el modelo las oculta dentro de 
otras primitivas de más alto nivel
Introducción
CONCURRENCIA CON PASO DE MENSAJES
8
• Existen muchos tipos concretos de modelos de 
paso de mensajes dependiendo de:
 Identificación de procesos al enviar o recibir 
(nombrado, direccionamiento, denominación…)
 Sincronización: Envío y recepción bloqueantes 
(síncronas) o no bloqueantes (asíncronas)
 Características del canal: Capacidad, tipo de datos, 
etc…
Introducción
CONCURRENCIA CON PASO DE MENSAJES
9
Concurrencia con Paso de Mensajes
• Introducción
• Identificación de procesos
• Sincronización
• Canal de comunicación
• Conclusiones
PROGRAMACIÓN CONCURRENTE
10
• Identificación de procesos
 La forma en la que el proceso emisor indica a qué 
proceso receptor va dirigido el mensaje y viceversa
 Operaciones:
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
 void send(Id receiverId, Message message)
 Message receive(Id senderId)
11
• Comunicación directa simétrica:
 Tanto el proceso emisor como el proceso receptor 
indican el id del otro proceso.
 Proceso A ejecuta: send(“B”, Message)
 Proceso B ejecuta: m = receive(“A”)
 Desventajas: 
 La identificación explícita de el receptor y el emisor 
puede hacer el sistema poco flexible a futuras 
ampliaciones
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
12
• Comunicación directa asimétrica:
 Normalmente el proceso emisor indica el id del receptor, 
pero no al contrario
 Proceso A ejecuta: send(“B”, Message)
 Proceso B ejecuta: m = receive()
 Ventajas:
 Es más flexible que el modelo simétrico
 Es el modelo usado en arquitectura cliente / servidor en 
sistemas distribuidos
 Habitualmente el receptor puede conocer qué emisor 
concreto le ha enviado cada mensaje
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
13
• Comunicación directa vs comunicación 
indirecta
 Comunicación directa: Los procesos identifican a 
otros procesos
 Comunicación indirecta: Los procesos emisores 
envían los mensajes a almacenes intermedios y los 
receptores los recogen de esos almacenes 
intermedios
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
14
• Comunicación indirecta
 La comunicación se realiza con almacenes 
intermedios
 Esos almacenes se denominan buzones o colas
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
 void send(Id queueId, Message message)
 Message receive(Id queueId)
15
• Comunicación indirecta
 Dependiendo de las restricciones en las colas y los 
mensajes existen diversos modelos:
 Varios procesos pueden recibir mensajes de una misma 
cola o cada cola puede ser exclusiva por proceso receptor
 Un mensaje enviado a una cola puede ser recibido por un 
único proceso (unicast) o por todos los procesos 
(multicast)
 Un mismo proceso puede enviar mensajes a varias colas
 La creación de colas puede ser estática o dinámica (en 
tiempo de ejecución)
Identificación de procesos
CONCURRENCIA CON PASO DE MENSAJES
16
Concurrencia con Paso de Mensajes
• Introducción
• Identificación de procesos
• Sincronización
• Canal de comunicación
• Conclusiones
PROGRAMACIÓN CONCURRENTE
17
• Tipos de comunicaciones en función de la 
sincronización:
 Comunicación síncrona: Emisor y receptor 
coinciden en el tiempo en el momento de la 
comunicación (ejemplo: teléfono)
 Comunicación asíncrona: Emisor y receptor 
tienen que coincidir en el tiempo para llevar a 
cabo la comunicación (ejemplo: mail)
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
18
• Bloqueo de envío y recepción
 Comunicación síncrona: 
 El emisor queda bloqueado en la operación de envío 
hasta que el receptor ejecute la operación de 
recepción
 El receptor queda bloqueado en la operación de 
recepción
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
Cita simple (rendez-vous)
19
• Bloqueo de envío y recepción
 Comunicación síncrona (caso especial): 
 El emisor queda bloqueado en la operación de envío 
hasta que el receptor ejecute la operación de 
recepción, procese el mensaje y envíe un mensaje de 
vuelta, que será recibido por el emisor.
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
Cita extendida (extended 
rendezvous)
20
• Bloqueo de envío y recepción
 Comunicación síncrona (tiempo de espera): 
 En algunos sistemas, las operaciones de envío y 
recepción bloqueantes disponen de versiones en las 
que se puede especificar un timeout.
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
21
• Bloqueo de envío y recepción
 Comunicación asíncrona: 
 El emisor no queda bloqueado en la operación de 
envío, los mensajes se guardan en un buffer hasta que 
el receptor los recoge
 El receptor puede ejecutar la operación de recepción 
sin bloquearse
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
22
• Bloqueo de envío y recepción
 Comunicación asíncrona (casos especiales): 
 El emisor podría quedarse bloqueado en la operación 
de envío si el buffer se llena de mensajes pendientes 
de recibir.
 El receptor podría querer bloquearse en la operación 
de recepción si no hay mensajes
Sincronización
CONCURRENCIA CON PASO DE MENSAJES
23
Concurrencia con Paso de Mensajes
• Introducción
• Identificación de procesos
• Sincronización
• Canal de comunicación
• Conclusiones
PROGRAMACIÓN CONCURRENTE
24
• Características del canal de comunicación
 Flujo de datos: bidireccional o unidireccional. La 
comunicación asíncrona suele ser unidireccional y la 
síncrona bidireccional.
 Capacidad del canal: Relevante en comunicación 
asíncrona (limitada o infinita dependiendo de los 
recursos HW)
Canal de comunicación
CONCURRENCIA CON PASO DE MENSAJES
25
• Características del canal de comunicación
 Tipo y tamaño de los mensajes: Algunos sistemas 
pueden poner restricciones de todos los mensajes 
del mismo tipo o del mismo tamaño.
 Ordenación y fiabilidad del envío: Los enlaces de 
red pueden ser menos
fiables que los canales 
internos en una máquina
Canal de comunicación
CONCURRENCIA CON PASO DE MENSAJES
26
Concurrencia con Paso de Mensajes
• Introducción
• Identificación de procesos
• Sincronización
• Canal de comunicación
• Conclusiones
PROGRAMACIÓN CONCURRENTE
27
• El modelo de paso de mensajes es interesante 
porque permite construir programas escalables 
(desde un nodo a un cluster)
• Existen una gran variedad de detalles en este 
modelo, y cada tecnología de desarrollo 
implementa modelos ligeramente diferentes
Conclusiones
CONCURRENCIA CON PASO DE MENSAJES
		Slide 1
		Concurrencia con Paso de Mensajes
		Introducción
		Introducción
		Introducción
		Introducción
		Introducción
		Introducción
		Concurrencia con Paso de Mensajes
		Identificación de procesos
		Identificación de procesos
		Identificación de procesos
		Identificación de procesos
		Identificación de procesos
		Identificación de procesos
		Concurrencia con Paso de Mensajes
		Sincronización
		Sincronización
		Sincronización
		Sincronización
		Sincronización
		Sincronización
		Concurrencia con Paso de Mensajes
		Canal de comunicación
		Canal de comunicación
		Concurrencia con Paso de Mensajes
		Conclusiones
__MACOSX/Academia/Tema3/._Tema3 - Concurrencia con paso de mensajes.pdf
Academia/Tema2/Tema 2. Memoria compartida.pdf
Programación
Concurrente
Tema 2
Memoria
Compartida
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
2
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
3
• SimpleConcurrent es una librería Java diseñada 
para la enseñanza de los conceptos básicos a la 
programación concurrente
• Soporta los modelos de concurrencia:
 Modelo de memoria compartida: Hilos y cerrojos
 Paso de mensajes: Actores
• Se puede usar junto con cualquier otra 
característica básica de Java (Clases, métodos, 
arrays, etc…)
SimpleConcurrent
MEMORIA COMPARTIDA
4
• Se ha diseñado para que sea lo más simple posible y que los 
programas sean muy compactos
• Se ha evitado el paradigma orientado a objetos para hacer los 
programas sencillos
• No se debe usar nunca en un programa real porque no está 
optimizada ni diseñada para ello
• Se puede descargar de github como fuente o binario 
http://www.github.com/codeurjc/simpleconcurrent
• Todos los ejemplos y ejercicios de este tema están en github 
https://github.com/codeurjc/concurrencia-tema2 
SimpleConcurrent
MEMORIA COMPARTIDA
http://www.github.com/codeurjc/simpleconcurrent
https://github.com/codeurjc/concurrencia-tema2
5
• Para usar la librería vamos a usar Maven
 Es una herramienta para gestionar proyectos Java
 Se descarga automáticamente las librerías 
(dependencias) de Internet
 Se puede usar en cualquier entorno de desarrollo 
actualizado (Eclipse Neon, Netbeans, IntelliJ…)
SimpleConcurrent
MEMORIA COMPARTIDA
6
• Cómo crear un proyecto Maven en Eclipse
 New project > Maven > Maven Project
 Marcar “Create simple project (skip archetype…)”
 Insertar los datos del proyecto
 Group Id: es.urjc.pc
 Artifact Id: ejercicios
 Finish
SimpleConcurrent
MEMORIA COMPARTIDA
7
• Cómo crear un proyecto Maven en Eclipse
SimpleConcurrent
MEMORIA COMPARTIDA
Nombre
del proyecto
Marcar
8
• El nuevo proyecto tiene la 
siguiente estructura
 src/main/java: Código de la 
aplicación
 src/test/java: Código de los 
tests
 pom.xml: Fichero de 
descripción del proyecto 
(nombre, dependencias, 
configuraciones, etc…)
SimpleConcurrent
MEMORIA COMPARTIDA
9
• pom.xml: Configuración del proyecto
SimpleConcurrent
MEMORIA COMPARTIDA
10
• pom.xml: Configuración del proyecto
Poner la vista de 
código fuente
SimpleConcurrent
MEMORIA COMPARTIDA
11
• pom.xml: Configuración del proyecto
 Todas las opciones del proyecto se configuran en el 
fichero pom.xml
 Configuración de Java 8
 Ficheros compatibles linux y windows (UTF-8):
<properties>
 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
 <maven.compiler.source>1.8</maven.compiler.source>
 <maven.compiler.target>1.8</maven.compiler.target>
</properties>
SimpleConcurrent
MEMORIA COMPARTIDA
12
• pom.xml: Configuración del proyecto
 Librerías (dependencias):
 Se indica el groupId, artifactId y versión de cada librería 
que use el proyecto
 Las librerías se descargan automáticamente
 Librería SimpleConcurrent versión 0.6
SimpleConcurrent
MEMORIA COMPARTIDA
<dependencies>
<dependency>
<groupId>com.github.codeurjc</groupId>
<artifactId>simpleconcurrent</artifactId>
<version>0.6</version>
</dependency>
</dependencies>
13
• pom.xml: Configuración del proyecto
 Repositorios de librerías:
 La mayoría de las librerías se descargan del repositorio por 
defecto (Maven Central)
 Otras librerías están en su propio repositorio
 Repositorio de SimpleConcurrent:
SimpleConcurrent
MEMORIA COMPARTIDA
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
14
SimpleConcurrent
MEMORIA COMPARTIDA
<project xmlns="http://maven.apache.org/POM/4.0.0" 
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>es.urjc.pc</groupId>
<artifactId>ejercicios</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.github.codeurjc</groupId>
<artifactId>simpleconcurrent</artifactId>
<version>0.6</version>
</dependency>
</dependencies>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
</project>
Librería 
SimpleConcurrent 
versión 0.5
Java 8
Repositorio 
de la librería
Datos del proyecto
pom.xml para usar 
SimpleConcurrent
15
• Cuidado! Algunos cambios en el fichero 
pom.xml no se reflejan el eclipse de forma 
automática
• Cuando se hace un cambio y eclipse no se 
actualiza con esos cambios, se tiene que 
actualizar manualmente
 Botón derecho proyecto > Maven > Update 
Project…
SimpleConcurrent
MEMORIA COMPARTIDA
16
SimpleConcurrent
•Memoria Compartida en SimpleConcurrent
 Modelo de Hilos y cerrojos
 La implementación de un programa concurrente 
con memoria compartida se divide en dos partes:
 1) Implementar el código que podrá ser ejecutado 
concurrentemente (programa secuencial)
 2) Iniciar la ejecución de ese código (crear el proceso)
MEMORIA COMPARTIDA
17
• Se importan los 
miembros estáticos de la 
clase SimpleConcurrent
• De esta forma, los 
métodos estáticos de 
SimpleConcurrent se 
pueden usar 
directamente, sin indicar 
el nombre de la clase
Programa Concurrente
SimpleConcurrent
package ejemplo;
import static 
es.urjc.etsii.code.
 concurrency.SimpleConcurrent.*;
public class Ejemplo1 {
public static void repeat(String text) {
for(int i=0; i<5; i++){println(text);}
}
public static void printText() {
println("B1");
println("B2");
println("B3");
}
public static void main(String[] args) {
createThread("repeat","XXXXX");
createThread("repeat","-----");
createThread("printText");
startThreadsAndWait();
}
} Download this code
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java
18
• Se escriben los códigos 
secuenciales como 
métodos estáticos de la 
clase principal
• Como cualquier método 
estático, pueden tener 
parámetros
• Usarán el método 
println(…) para imprimir 
por pantalla
Programa Concurrente
package ejemplo;
import static 
es.urjc.etsii.code.
 concurrency.SimpleConcurrent.*;
public class Ejemplo1 {
public static void repeat(String text) {
for(int i=0; i<5; i++){println(text);}
}
public static void printText() {
println("B1");
println("B2");
println("B3");
}
public static void main(String[] args) {
createThread("repeat","XXXXX");
createThread("repeat","-----");
createThread("printText");
startThreadsAndWait();
}
}
SimpleConcurrent
Download this code
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java
19
• En el método main se crea un 
nuevo hilo con el método 
createThread(…)
• Se indica el nombre del método 
que será ejecutado en ese 
nuevo hilo
• Se pueden pasar parámetros
• Es posible crear varios hilos que 
ejecutan el mismo método, con 
distintos parámetros
• El método 
startThreadsAndWait () crea 
los hilos y se bloquea hasta que 
han terminado su ejecución
Programa Concurrente
package ejemplo;
import static 
es.urjc.etsii.code.
 concurrency.SimpleConcurrent.*;
public class Ejemplo1 {
public static void repeat(String text) {
for(int i=0; i<5; i++){println(text);}
}
public static void printText() {
println("B1");
println("B2");
println("B3");
}
public static void main(String[] args) {
createThread("repeat","XXXXX");
createThread("repeat","-----");
createThread("printText");
startThreadsAndWait();
}
}
SimpleConcurrent
Download this code
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://gist.github.com/micaelgallego/6575162
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/Ejemplo1.java
20
• Al ejecutar el 
programa varias 
veces se obtienen las 
siguientes salidas por 
pantalla
• Como se puede ver, 
se obtienen 
diferentes 
resultados en 
diferentes 
ejecuciones
-----
XXXXX
B1
-----
XXXXX
-----
B2
-----
XXXXX
B3
XXXXX
-----
XXXXX
Programa Concurrente
B1
-----
XXXXX
XXXXX
B2
-----
XXXXX
-----
B3
XXXXX
-----
XXXXX
-----
-----
XXXXX
B1
XXXXX
B2
XXXXX
-----
XXXXX
B3
-----
-----
XXXXX
-----
B1
B2
-----
XXXXX
B3
-----
-----
XXXXX
-----
XXXXX
-----
XXXXX
XXXXX
SimpleConcurrent
MEMORIA COMPARTIDA
21
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
22
Intercalación de instrucciones: Indeterminismo
• El orden de las instrucciones
 En un programa secuencial, todas las instrucciones 
están ordenadas
 Está definido el orden en el que se van ejecutando 
las instrucciones y la forma en la que van 
cambiando los valores de las variables
 En la programación concurrente, diferentes 
ejecuciones del mismo programa pueden ejecutar 
las sentencias en orden diferente
MEMORIA COMPARTIDA
23
El orden de las instrucciones
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
package ejemplo;
import static es.urjc.etsii.code.
 concurrency.SimpleConcurrent.*;
public class MaxMin {
static volatile double n1, n2, min, max;
public static void min(){
min = n1 < n2? n1 : n2;
}
public static void max(){
max = n1 > n2? n1 : n2;
}
public static void main(String[] args) {
 n1=3; n2=5; // I0
 min(); // I1
 max(); // I2
 println("m:"+min+" M:"+max); // I3
}
}
I0 I1 I2 I3
La Relación de Precedencia (->) 
entre las instrucciones define una 
relación de orden
I0 -> I1 -> I2 -> I3
Programación 
Secuencial 
Orden Total
Diagrama de Precedencia
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/MaxMin.java
24
El orden de las instrucciones
• Existe determinismo
• Al ejecutar el programa con los mismos datos de entrada se 
obtienen los mismos resultados
• Hay veces que no es necesario que una sentencia sea 
ejecutada antes que otra, se podrían ejecutar en cualquier 
orden ¿Cómo lo podríamos especificar en el código?
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
Programación Secuencial 
Orden Total
I0 I1 I2 I3
Diagrama de Precedencia
25
El orden de las instrucciones
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
I0
I1
I2
I3
I0->I1, I0–>I2, I1->I3,I2->I3
I1 || I2
Programación 
Concurrente 
Orden Parcial
Diagrama de Precedencia
package ejemplo;
import static es.urjc.etsii.code.
 concurrency.SimpleConcurrent.*;
public class MaxMinCon {
static volatile double n1, n2, min, max;
public static void min(){
min = n1 < n2? n1 : n2;
}
public static void max(){
max = n1 > n2? n1 : n2;
}
public static void main(String[] args) {
 n1 =3; n2 =5; // I0
 createThread("min"); // I1
 createThread("max"); // I2
 startThreadsAndWait();
 println("m:"+min+" M:"+max); // I3
}
} Download the code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/MaxMinCon.java
26
El orden de las instrucciones
• No existe determinismo
• No se restringe el orden de ejecución de I1 e I2. 
Podrían ejecutarse en cualquier ordena o de forma 
concurrentemente
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
I0
I1
I2
I3
I1 || I2
Diagrama de Precedencia
Programación 
Concurrente 
Orden Parcial
27
Instrucciones atómicas
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
1ª Abstracción de la Programación Concurrente
Se considera que cada proceso se ejecuta en su 
propio procesador
2ª Abstracción de la Programación Concurrente
Se ignoran las velocidades relativas de cada 
proceso, lo que posibilita considerar sólo las 
secuencias de instrucciones que se ejecutan 
28
Instrucciones atómicas
• La 1ª y 2ª abstracción permiten abstraernos de 
detalles como el número de procesadores y su 
velocidad
• Nos permiten centrarnos en las diferentes 
posibles secuencias de instrucciones que 
ejecuta cada proceso
• Pero… ¿Exactamente qué instrucciones 
ejecuta un proceso?
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
29
Instrucciones atómicas
• Una instrucción atómica es aquella cuya 
ejecución es indivisible
• Independientemente del número de 
procesadores, una instrucción atómica de un 
proceso se ejecuta completamente sin 
interferencias 
• Durante la ejecución de una sentencia atómica, 
otros procesos no pueden interferir en su 
ejecución
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
30
Instrucciones atómicas
• Las instrucciones atómicas se usan mucho 
aplicaciones empresariales
• Ejemplo: Reserva de vuelo con escala
 Un viajero quiere ir de Madrid a Los Ángeles
 Tiene que hacer escala en New York
 Se debe reservar el billete Madrid-New York y también de 
New York-Los Ángeles
 La reserva no se puede quedar a medias, reservando sólo un 
trayecto
 O se reservan ambos trayectos o no se reserva ninguno
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
31
Instrucciones atómicas
• En programación concurrente, es muy importante
conocer las instrucciones atómicas que ejecuta el 
procesador
• Esas instrucciones atómicas serán las que se ejecuten 
completamente sin interferencias de otros procesos
• Para desarrollar aplicaciones concurrentes en un lenguaje 
de programación, hay que conocer qué sentencias del 
lenguaje se corresponden a sentencias atómicas
• Las sentencias no atómicas ejecutadas por un proceso, 
podrán verse interferidas por la ejecución de otros 
procesos
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
32
Instrucciones atómicas
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
x = x+1
LOAD R,x
ADD R,#1
STR R,x
Sentencia Java
Instrucciones Atómicas
Corresponde a las instrucciones 
atómicas
• Incrementar una variable en 1 no es 
una sentencia atómica en Java
• Carga la variable x en el registro R del 
procesador
• Suma 1 al registro R del procesador 
• Guarda el valor del registro R en la variable x
33
Intercalación (Interleaving)
• La 1ª y 2ª abstracción nos permiten pensar en la 
secuencia de instrucciones que ejecuta cada proceso 
en su procesador
• Pero es bastante complicado pensar en la ejecución en 
paralelo de múltiples secuencias de instrucciones (una 
secuencia por cada proceso)
• Para que sea más sencillo estudiar el comportamiento 
de un programa concurrente, se considera que todas 
las sentencias de todos los procesos se intercalan en 
una única secuencia
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
34
Intercalación (Interleaving)
• No hay solapamientos
• La ejecución de dos instrucciones atómicas en paralelo 
tiene los mismos resultados que una después de otra 
(de forma secuencial)
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
3ª Abstracción de la Programación Concurrente
Se considera que las secuencias de ejecución de las 
acciones atómicas de todos los procesos se 
intercalan en una única secuencia
35
Intercalación (Interleaving)
• Hay que estudiar lo que ocurre en todas las 
posibles intercalaciones de instrucciones 
atómicas (y son muchas)
• Así comprenderemos todas las posibles 
ejecuciones del programa (y comprobaremos si 
son correctas)
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
36
Intercalación (Interleaving)
•Multiprogramación
 Realmente las instrucciones se ejecutan de forma 
intercalada porque sólo hay un procesador
•Multiproceso
 Si dos instrucciones compiten por un mismo recurso, el 
hardware se encarga de que se ejecuten de forma 
secuencial
 Si dos instrucciones no compiten, son independientes, el 
resultado es el mismo en paralelo que de forma secuencial
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
37
Intercalación (Interleaving)
• Todas las abstracciones se pueden resumir en una sola
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
Abstracción de la Programación Concurrente
Es el estudio de las secuencias de ejecución 
intercalada de las instrucciones atómicas de los 
procesos secuenciales
Principles of Concurrent and Distributed Programming, 2nd ed. 2006 Addison-Wesley. Ben-Ari.
38
Indeterminismo
• Al ejecutar un programa concurrente, se ejecutarán 
las sentencias con una intercalación determinada y se 
obtendrá un resultado
• Puesto que todas las intercalaciones de instrucciones 
atómicas son posibles, un mismo programa puede 
obtener resultados diferentes en diferentes 
ejecuciones
• Cuando un mismo programa obtiene resultados 
diferentes dependiendo de la ejecución concreta, se 
dice que es indeterminista
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
39
Indeterminismo
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
public class IncDec {
static volatile double x;
public static void inc(){ x = x + 1; }
public static void dec(){ x = x - 1; }
public static void main(String[] args){
 x = 0;
 
 createThread("inc");
 createThread("dec");
 startThreadsAndWait();
 
 println("x:"+x);
}
}
LOAD R,x
ADD R,#1
STR R,x
Instrucciones atómicas 
del tipo de proceso inc
LOAD R2,x
SUB R2,#1
STR R2,x
Instrucciones atómicas 
del tipo de proceso dec
Download the code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDec.java
40
Indeterminismo
• Una posible intercalación de instrucciones
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
inc dec x R R2
1 LOAD R2,x 0 0
2 LOAD R,x 0 0 0
3 SUB R2,#1 0 0 -1
4 ADD R,#1 0 1 -1
5 STR R2,x -1 1 -1
6 STR R,x 1 1 -1
Resultado Final: 1
LOAD R,x
ADD R,#1
STR R,x
inc
LOAD R2,x
SUB R2,#1
STR R2,x
dec
41
Indeterminismo
• Otra posible intercalación de instrucciones
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
inc dec x R R2
1 LOAD R2,x 0 0
2 LOAD R,x 0 0 0
3 ADD R,#1 0 1 0
4 SUB R2,#1 0 1 -1
5 STR R,x 1 1 -1
6 STR R2,x -1 1 -1
Resultado Final: -1
LOAD R,x
ADD R,#1
STR R,x
inc
LOAD R2,x
SUB R2,#1
STR R2,x
dec
42
Indeterminismo
• Otra más…
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
LOAD R,x
ADD R,#1
STR R,x
inc
LOAD R2,x
SUB R2,#1
STR R2,x
dec
inc dec x R R2
1 LOAD R2,x 0 0
2 SUB R2,#1 0 -1
3 STR R2,x -1 -1
4 LOAD R,x -1 -1 -1
5 ADD R,#1 -1 0 -1
6 STR R,x 0 0 -1
Resultado Final: 0
Como se deben considerar todas las 
posibles intercalaciones, también hay que 
considerar que un proceso se ejecute 
completamente antes que el otro
43
Indeterminismo
• Se han encontrado tres intercalaciones posibles de 
las instrucciones atómicas de un programa 
concurrente en las que se obtiene un resultado 
diferente
• Hay 20 posibles intercalaciones
 En sólo 2 de ellas se obtiene un 0
 En 9 intercalaciones se obtiene un 1
 En 9 intercalaciones se obtiene un -1
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMO
44
Indeterminismo
INTERCALACIÓN DE INSTRUCCIONES: INDETERMINISMOpublic class IncDecFor {
static volatile double x;
public static void inc(){
 for (int i = 0; i < 10000000; i++){
 x = x + 1; 
 }
}
public static void dec(){ 
 for (int i = 0; i < 10000000; i++) {
 x = x - 1; 
 }
 }
public static void main(String[] args){
 x = 0; 
 createThread("inc");
 createThread("dec");
 startThreadsAndWait(); 
 println("x:"+x);
}
}
• Los compiladores y 
máquinas virtuales pueden 
hacer ciertas 
optimizaciones al 
intercalar las instrucciones 
de los procesos
• Para observar los efectos 
de la intercalación de 
instrucciones en el ejemplo 
anterior se pueden ejecutar 
múltiples incrementos y 
decrementos en cada 
proceso
Download the code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecFor.java
45
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
46
Variables Compartidas
•Modelo de memoria compartida Los procesos 
pueden acceder a una memoria común
• Existen variables compartidas que varios 
procesos pueden leer y escribir
• Un valor escrito por un proceso puede ser 
leído por otro proceso
MEMORIA COMPARTIDA
¿Qué ocurre si dos procesos leen o 
escriben de forma simultánea en la 
misma variable?
47
• La abstracción de la programación 
concurrente nos indica que todas las 
instrucciones atómicas de cada proceso se 
intercalan en una única secuencia
• Con SimpleConcurrent, la lectura y la escritura 
de atributos compartidos de tipo simple 
(boolean, int, char…) son instrucciones 
atómicas en Java
Variables Compartidas
MEMORIA COMPARTIDA
48
• Lectura simultáneas
 Ambos procesos leen el valor de la variable
 No interfieren entre sí
• Escrituras simultáneas
 El resultado de la escritura simultánea sobre una 
variable compartida será el valor escrito por uno u 
otro proceso
 Nunca una mezcla de las dos escrituras
Variables Compartidas
MEMORIA COMPARTIDA
49
• Lectura y Escritura simultáneas
 Si un proceso lee sobre una variable compartida y 
simultáneamente otro escribe sobre ella, el 
resultado de la lectura será:
 O el valor de la variable antes de la escritura 
 O el valor de la variable después de la escritura
 Nunca un valor intermedio
Variables Compartidas
MEMORIA COMPARTIDA
50
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
51
Memoria Compartida
• Sincronización con Espera Activa
 Relaciones entre procesos
 Sincronización Condicional
 Exclusión Mutua
PROGRAMACIÓN CONCURRENTE
52
• Relaciones entre procesos concurrentes
 Independientes 
 Son gestionados por el Sistema
 No nos tenemos que preocupar como 
programadores.
 Interactúan entre sí 
 Compiten por los recursos o se comunican entre sí 
para obtener un resultado conjunto
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
53
• Interacción
 Competencia: Varios procesos deben compartir 
recursos comunes del sistema (procesador, memoria, 
disco, impresoras,…) por lo que compiten entre ellos 
para conseguirlo
 Cooperación: Varios procesos deben trabajar sobre 
distintas partes de un problema para resolverlo 
conjuntamente
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
54
• Dos procesos que interactúan entre sí lo hacen a 
través de las siguientes actividades
 Comunicación: Es el intercambio de información entre 
procesos. Dos procesos cooperan entre sí intercambian 
información de algún modo
 Sincronización: La sincronización impone restricciones 
a la ejecución de las sentencias de los procesos (espera)
 Sincronización condicional
 Exclusión mutua
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
55
• Sincronización Condicional
 Uno o más procesos no pueden continuar su 
ejecución (tienen que esperarse) a que se cumpla 
cierta condición
 Otro proceso es el que establece esa condición
Un proceso servidor web está esperando a que otro proceso 
navegador web realice una petición mediante http
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
56
• Exclusión Mutua
 Varios procesos compiten por un recurso común de acceso 
exclusivo
 Sólo uno de los procesos puede estar accediendo al recurso 
a la vez 
 Si varios procesos quieren usar el recurso, tienen que 
esperarse a que quede libre
 Los procesos se excluyen mutuamente
Sólo un proceso puede acceder a la vez a un recurso como un 
lector de huellas digitales
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
57
Relaciones 
entre procesos
Competencia
Cooperación
Actividades 
entre procesos
Sincronización
Comunicación
Sincronización
Condicional
Exclusión
Mutua
Se lleva a 
cabo
mediante
A veces 
necesita
Tipos
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
58
• Comunicación en el modelo de memoria 
compartida
 Se utilizan las variables o atributos compartidos 
para compartir información
 Un proceso escribe un valor en la variable y otro 
proceso lee ese valor
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
59
• Sincronización en el modelo de memoria 
compartida
 Espera activa: Utiliza únicamente variables 
compartidas para sincronizarse
 Herramientas de sincronización: Cuando se usan, 
además de variables compartidas, herramientas de 
sincronización entre procesos (Semáforos, 
monitores, etc…)
Relaciones entre procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
60
Memoria Compartida
• Sincronización con Espera Activa
 Relaciones entre procesos
 Sincronización Condicional
 Exclusión Mutua
PROGRAMACIÓN CONCURRENTE
61
Sincronización Condicional
• La Sincronización Condicional se produce cuando un proceso 
debe esperar a que se cumpla una cierta condición para 
proseguir su ejecución
• Esta condición sólo puede ser activada por otro proceso
SINCRONIZACIÓN CON ESPERA ACTIVA
61
PA1 PA2
PB1 PB2
Diagrama de Precedencia
Proceso A
Proceso B
62
Sincronización Condicional
• Si los procesos muestran por pantalla* el nombre de la acción que han 
realizado, el resultado de ejecutar el programa concurrente del diagrama 
podría ser cualquiera de los siguientes:
SINCRONIZACIÓN CON ESPERA ACTIVA
PA1 PA2
PB1 PB2
Proceso A
Proceso B
PA1 PA2 PB1 PB2
PA1 PB1 PA2 PB2
PB1 PA1 PA2 PB2
Salidas Posibles:
Salidas No Posibles:
PB1 PB2 PA1 PA2 * La escritura en pantalla con println es una 
instrucción atómica en SimpleConcurrent
63
Sincronización
Condicional
BLP (06-07) 63
public class SincCond {
static volatile boolean continuar;
public static void a() {
print("PA1 ");
continuar = true;
print("PA2 ");
}
public static void b() {
print("PB1 ");
while (!continuar);
print("PB2 ");
}
public static void main(String[] args){
continuar = false;
createThread("a");
createThread("b");
startThreadsAndWait();
}
}
Se declara un atributo 
compartido continuar 
El proceso a la pondrá a true 
cuando haya ejecutado PA1 y 
continuará ejecutando PA2
El proceso b ejecutará PB1 y 
comprobará repetidamente 
en un bucle el valor de 
continuar
Saldrá del bucle cuando 
valga true y continuará 
ejecutando PB2
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/SincCond.java
64
Sincronización Condicional
a b continuar
1 print("PA1 "); false
2 print("PB1 "); false
3 continuar = true; true
4 print("PA2 "); true
5 while (!continuar); true
6 print("PB2 "); true
SINCRONIZACIÓN CON ESPERA ACTIVA
PA1 PB1 PA2 PB2Salida:
Posible intercalación de instrucciones
65
Sincronización Condicional
a b continuar
1 print("PB1 "); false
2 while (!continuar); false
3 while (!continuar); false
4 while (!continuar); false
5 print("PA1 "); false
6 continuar = true; true
7 print("PA2 "); true
8 while (!continuar); true
9 print("PB2 "); true
SINCRONIZACIÓN CON ESPERA ACTIVA
PB1 PA1 PA2 PB2Salida:
Posible intercalación de instrucciones
66
Sincronización Condicional
a b continuar
1 print("PA1 "); false
2 continuar = true; true
3 print("PA2 "); true
4 print("PB1 "); true
5 while (!continuar); true
6 print("PB2 "); true
SINCRONIZACIÓN CON ESPERA ACTIVA
PA1 PA2 PB1 PB2Salida:
Posible intercalación de instrucciones
67
Ejercicio 1 – Productor Consumidor
• Se desea implementar un programa 
concurrente con un proceso que produce 
información (productor) y otro proceso que 
hace uso de esa información (consumidor) 
• El proceso productor genera un número 
aleatorio y termina 
• El proceso consumidor muestra por pantalla el 
número generado y termina
SINCRONIZACIÓN CONDICIONAL
68
Ejercicio 2 – Productor Consumidor N
• Se desea ampliar el programa anterior de 
forma que el proceso productor genere 5 
números (consecutivos)
• El proceso consumidor consumirá esos 5 
números
• No se puede quedar ningún producto sin 
consumir
• No se puede consumir dos veces el mismo 
producto 
SINCRONIZACIÓN CONDICIONAL
69
Ejercicio 3 – Cliente Servidor 1 Petición
• Programa formado por un proceso servidor y otro 
proceso cliente
• El proceso cliente hace una petición al proceso servidor 
(con un número entero) y espera su respuesta. Cuando la 
recibe, la procesa.
• El proceso servidor no hace nada hasta que recibe una 
petición, momento en el que suma 1 y devuelve el valor. 
• El proceso cliente muestra un número por pantalla, se lo 
envía al servidor y cuando le llega la respuesta, la
muestra 
por pantalla
SINCRONIZACIÓN CONDICIONAL
70
Ejercicio 4 – Cliente Servidor N Peticiones
• Se desea ampliar el programa anterior de 
forma que el proceso cliente esté 
constantemente haciendo peticiones y el 
proceso servidor atendiendo a las mismas
SINCRONIZACIÓN CONDICIONAL
71
Ejercicio 4B – N Clientes Servidor N Peticiones
• Se desea ampliar el programa anterior de 
forma que existan varios clientes que acceden 
al mismo servidor
• Para facilitar la depuración, cada proceso 
cliente debe empezar los números 
consecutivos en un valor diferente (de 10 en 10)
SINCRONIZACIÓN CONDICIONAL
72
Memoria Compartida
• Sincronización con Espera Activa
 Relaciones entre procesos
 Sincronización Condicional
 Exclusión Mutua
PROGRAMACIÓN CONCURRENTE
73
Exclusión Mutua
• ¿Qué es la Exclusión Mutua?
 Es un tipo de sincronización entre procesos que se 
tiene cuando varios procesos compiten por un 
recurso común de acceso exclusivo
 Sólo uno de los procesos puede estar accediendo al 
recurso a la vez y los demás tienen que esperar
 Cuando un proceso libera el recurso, otro proceso 
que estuviera esperando podrá acceder a él
SINCRONIZACIÓN CON ESPERA ACTIVA
74
Exclusión Mutua
• ¿Qué es el Problema de la Exclusión Mutua?
 Es un problema histórico, definido por los primeros 
investigadores en Programación Concurrente
 El problema se utilizó para investigar la mejor 
forma de implementar la sincronización de tipo 
exclusión mutua usando únicamente variables 
compartidas (espera activa)
SINCRONIZACIÓN CON ESPERA ACTIVA
75
Exclusión Mutua
• El problema de la 
Exclusión Mutua
 Se tienen dos o más procesos 
concurrentes, que ejecutan 
indefinidamente una secuencia 
de instrucciones dividida en dos 
secciones
 Sección bajo exclusión mutua
 Sección sin exclusión mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
public static void p1() {
while(true) {
// Sección bajo EM
printlnI("P1_EM1");
printlnI("P1_EM2");
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
}
El método printlnI imprime en una columna diferente la 
información de cada proceso. Esto permite diferenciar de forma muy 
sencilla qué instrucciones ejecuta cada proceso.
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutua.java
76
Exclusión Mutua
• Sección bajo Exclusión Mutua (Sección crítica)
 Secuencia de instrucciones que acceden a un recurso compartido 
de acceso exclusivo
 Puesto que el recurso es de acceso exclusivo y solo un proceso 
puede acceder a él al mismo tiempo, cada proceso debe ejecutar 
las instrucciones de la sección bajo EM sin que haya 
intercalación de instrucciones de la sección bajo EM de otros 
procesos
 Se pueden intercalar instrucciones que no hagan uso del recurso, 
es decir, instrucciones de la sección sin EM de otros procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
77
Exclusión Mutua
• Sección sin Exclusión Mutua
 Secuencia de instrucciones que pueden ser 
ejecutadas concurrentemente por todos los 
procesos
 Sus instrucciones se pueden intercalar con las 
instrucciones de la sección bajo EM de otros 
procesos
SINCRONIZACIÓN CON ESPERA ACTIVA
78
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
78
P1_EM1 
P1_EM2 
P1_1 
 P2_EM1 
P1_2 
 P2_EM2 
 P2_1 
P1_EM1 
 P2_2 
P1_EM2 
P1_1 
 P2_EM1 
 P2_EM2 
 P2_1 
P1_2 
P1_EM1 
...
 Ejecución de ejemplo con dos 
procesos ejecutando instrucciones 
de la sección bajo EM y a 
continuación instrucciones de la 
sección sin EM
 No se intercalan sentencias de las 
secciones bajo EM de los dos 
procesos
 Sí se intercalan instrucciones de las 
secciones sin EM
79
Exclusión Mutua
• Implementación de la Exclusión Mutua con 
espera activa
 No es fácil de implementar
 Veremos dos intentos de implementación pero 
que no funcionan correctamente
 El estudio de estos intentos nos permitirá entender 
los errores habituales de los programas 
concurrentes (y más adelante veremos cómo 
evitarlos)
SINCRONIZACIÓN CON ESPERA ACTIVA
80
Primer intento
• Se puede usar una variable booleana que indique si hay 
algún proceso ejecutando las secciones que deben estar 
bajo exclusión mutua (para que los demás se esperen)
• Un proceso, antes de ejecutar las instrucciones bajo 
exclusión mutua, comprueba si ya hay otro proceso 
mirando esa variable
• Si hay otro proceso, espera hasta que termine
• Si no hay ningún proceso en, pone a true la variable y 
empieza a ejecutar las instrucciones
• Cuando termina, vuelve a poner a false la variable
SINCRONIZACIÓN CON ESPERA ACTIVA
8181
static volatile boolean ocupado;
for (int i = 0; i < 5; i++) {
while (ocupado);
ocupado = true;
// Sección bajo EM
printlnI("P1_EM1");
printlnI("P1_EM2");
ocupado = false;
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
for (int i = 0; i < 5; i++) {
while (ocupado);
ocupado = true;
// Sección bajo EM
printlnI("P2_EM1");
printlnI("P2_EM2");
ocupado = false;
// Sección sin EM
printlnI("P2_1");
printlnI("P2_2");
}
Primer intento
EXCLUSIÓN MUTUA
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaIntento1.java
82
p1 p2 ocupado
1 while (ocupado); false
2 ocupado = true; true
3 while (ocupado); true
4 printlnI("P1_EM1"); true
5 while (ocupado); true
6 printlnI("P1_EM2"); true
7 while (ocupado); true
8 ocupado = false; false
9 while (ocupado); false
10 printlnI("P1_1"); false
11 ocupado = true; true
12 printlnI("P2_EM1"); true
13 printlnI("P1_2"); true
14 while (ocupado); true
15 printlnI("P2_EM2"); true
16 while (ocupado); true
17 ocupado = false; false
18 while (ocupado); false
19 …
83
• La intercalación que hemos visto se comporta 
correctamente
• El problema es que existen intercalaciones que 
no funcionan correctamente
• Los dos procesos pueden ejecutar las 
instrucciones que deberían estar bajo 
exclusión muta de forma intercalada
Primer intento
EXCLUSIÓN MUTUA
84
p1 p2 ocupado
1 while (ocupado); false
2 while (ocupado); false
3 ocupado = true; true
4 ocupado = true; true
5 printlnI("P1_EM1"); true
6 printlnI("P2_EM1"); true
7 printlnI("P1_EM2"); true
8 printlnI("P2_EM2"); true
9 …
Primer intento
EXCLUSIÓN MUTUA
• Intercalación problemática 
 Se permite que dos procesos ejecuten las sentencias de la 
EM de forma intercalada
85
• El problema del intento anterior es que los dos 
procesos miran y después entran, pudiendo 
entrar los dos a la vez
• En el segundo intento antes de comprobar si 
hay alguien dentro, el proceso pide ejecutar 
instrucciones bajo exclusión mutua
• Si alguien lo ha pedido ya, el proceso se 
espera
Segundo intento
EXCLUSIÓN MUTUA
8686
static volatile boolean p1p;
static volatile boolean p2p;
for (int i = 0; i < 5; i++) {
p1p = true;
while (p2p);
// Sección bajo EM
printlnI("P1_EM1");
printlnI("P1_EM2");
p1p = false;
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
for (int i = 0; i < 5; i++) {
p2p = true;
while (p1p);
// Sección bajo EM
printlnI("P2_EM1");
printlnI("P2_EM2");
p2p = false;
// Sección sin EM
printlnI("P2_1");
printlnI("P2_2");
}
Segundo intento
EXCLUSIÓN MUTUA
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaIntento2.java
87
p1 p2 p1p p2p
1 p1p = true; true false
2 while (p2p); true false
3 p2p = true; true true
4 while (p1p); true true
5 printlnI("P1_EM1"); true true
6 while (p1p); true true
7 printlnI("P1_EM2"); true true
8 p1p = false; false true
9 while (p1p); false true
10 printlnI("P1_1"); false true
11 printlnI("P1_2"); false true
12 printlnI("P2_EM1"); false true
13 p1p = true; true true
14 while (p2p); true true
15 printlnI("P2_EM2"); true true
16 p2p = false; true false
17 while (p2p); true false
18 printlnI("P1_EM1 "); true false
19 …
88
Segundo intento
• Intercalación problemática 
 Si los dos procesos anotan su petición a la vez, se 
quedan esperando para siempre (interbloqueo)
EXCLUSIÓN MUTUA
88
p1 p2 c.p1p c.p2p
1 p1p = true; true false
2 p2p = true; true true
3 while (p2p); true true
4 while (p1p); true true
5 while (p1p); true true
6 while (p1p); true true
7 while (p2p); true true
89
Exclusión Mutua
• Los problemas que hemos visto son típicos al 
empezar a programar de forma concurrente
• Hay que tener muy presentes todas las posibles 
intercalaciones de las instrucciones
• Por muy baja sea la probabilidad de que algo malo 
ocurra, puede ocurrir en cualquier momento
• Ejecutar el programa varias veces sin problemas no 
es garantía de que no tenga problemas
SINCRONIZACIÓN CON ESPERA ACTIVA
90
Exclusión Mutua
• Existen varios algoritmos para implementar 
sincronización de Exclusión Mutua con espera activa 
 El algoritmo de Dekker: se utiliza para dos procesos
 El algoritmo de Lamport: es un algoritmo genérico 
diseñado para N procesos
• Estos algoritmos son bastante complejos y no los 
vamos a estudiar
• Información sobre ellos:
SINCRONIZACIÓN CON ESPERA ACTIVA
Ben-Ari, M. Principles of Concurrent and 
Distributed Programming. 2nd Ed. Prentice Hall, 
2.006.
91
• SimpleConcurrent implementa la sincronización de 
Exclusión Mutua:
 Para entrar en la sección bajo EM:
 Para salir de la sección bajo EM:
enterMutex();
exitMutex();
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
92
public static void p1() {
for (int i = 0; i < 5; i++) {
 // Sección bajo EM
enterMutex();
printlnI("P1_EM1");
printlnI("P1_EM2");
exitMutex();
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
}
public static void p2() {
for (int i = 0; i < 5; i++) {
// Sección bajo EM
enterMutex();
printlnI("P2_EM1");
printlnI("P2_EM2");
exitMutex();
// Sección sin EM
printlnI("P2_1");
printlnI("P2_2");
}
}
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaEA.java
93
• Programa que 
muestra El 
problema de la 
Exclusión Mutua 
de forma textual 
al ejecutarse
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
public class ExcMutuaEA2 {
public static void p() {
for (int i=0; i<5; i++) {
enterMutex();
 printlnI("********");
printlnI("********");
printlnI("********");
exitMutex();
printlnI("--------");
printlnI("--------");
printlnI("--------");
printlnI("--------");
printlnI("--------");
}
}
public static void main(String[] args){
createThreads(4,"p");
startThreadsAndWait();
}
}
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaEA2.java
94
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA . ********
 ********
 ********
 ********
 --------
 --------
 ********
 --------
 ********
 --------
 --------
 ********
 --------
 --------
 ********
 --------
 ********
 --------
********
 --------
 --------
********
 --------
********
--------
 ********
 --------
--------
 --------
 ********
--------
 ********
 --------
--------
 --------
 ********
• Salida por pantalla de 
una de las posibles 
ejecuciones del 
programa
• Se puede observar como 
los asteriscos nunca se 
intercalan porque 
corresponden a la 
sección crítica (están 
bajo exclusión mutua)
95
• Normalmente la sección bajo exclusión 
mutua se usa para acceder de forma exclusiva 
a una variable compartida
• De esa forma no aparecen interferencias 
entre varios hilos accediendo a la misma vez
• Una misma variable compartida se puede usar 
en varios partes del programa
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
96
• Variable x bajo exclusión 
mutua con dos secciones 
diferentes (incremento y 
decremento)
• El resultado de este 
programa siempre será 
correcto (cero)
• No se pueden producir 
errores al contar porque 
no se pueden intercalar 
de forma anómala 
instrucciones que usan la 
variable x
public class IncDecEM {
static volatile double x;
public static void inc() {
for (int i = 0; i < 10000000; i++){
enterMutex();
x = x + 1;
exitMutex();
}
}
public static void dec() {
for (int i = 0; i < 10000000; i++){
enterMutex();
x = x - 1;
exitMutex();
}
}
}
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecEM.java
97
• Varios recursos diferentes pueden estar bajo 
exclusión mutua en un mismo programa
• Las secciones críticas de cada recurso son 
completamente independientes entre sí. Incluso 
pueden intercalarse sus instrucciones
• Las secciones críticas de diferentes recursos pueden 
anidarse si es necesario
• Se debe indicar el nombre del recurso como un 
parámetro de enterMutex(…) y exitMutex(…)
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
98
• Las variables x e y están 
bajo exclusión mutua con 
dos secciones 
independientes
• Se pueden intercalar 
instrucciones que usan 
variables diferentes
• No se pueden intercalar 
instrucciones que usen la 
misma variable
• El resultado final en cada 
variable es el correcto 
(cero)
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVApublic class IncDecEM2 {
static volatile double x, y;
public static void incX() {
enterMutex("x"); 
x = x + 1;
exitMutex("x");
}
public static void decX() {
enterMutex("x");
x = x - 1;
exitMutex("x");
}
public static void incY() {
enterMutex("y");
y = y + 1;
exitMutex("y");
}
public static void decY() {
enterMutex("y");
y = y - 1;
exitMutex("y");
}
} Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/IncDecEM2.java
99
Ejercicio 5 - Museo
• Existen 3 personas en el mundo, 1 museo, y sólo cabe una 
persona dentro del museo
• Las personas realizan cuatro acciones dentro del museo
 Cuando entran al museo saludan: “hola!”
 Cuando ven el museo se sorprenden: “qué bonito!” y 
“alucinante!”
 Cuando se van del museo se despiden: “adiós”
• Cuando salen del museo se van a dar un “paseo”
• Después del paseo, les ha gustado tanto que vuelven a 
entrar
EXCLUSIÓN MUTUA
100
Ejercicio 5 - Museo
• Se pide:
 Tipos de procesos
 Número de procesos del programa concurrente y de qué 
tipo son
 Escribir lo que hace cada proceso
 Identificar los recursos compartidos
 Identificar la sección o secciones críticas
 Escribir el programa completo
EXCLUSIÓN MUTUA
101
Ejercicio 6 – Museo con infinitas personas
• Considerar que caben infinitas personas dentro 
del museo
• Cada persona al entrar tiene que saludar 
diciendo cuántas personas hay en el museo: 
“hola, somos 3”
• Al despedirse tiene que decir el número de 
personas que quedan tras irse: “adiós a los 2”
EXCLUSIÓN MUTUA
102
Ejercicio 7 – Museo con regalo
• Para incentivar las visitas, cuando una persona entre 
en el museo estando vacío, será obsequiado con un 
regalo
• Las personas, después de saludar, dicen si les han 
dado un regalo (“Tengo regalo”) o si no (“No tengo 
regalo”)
• Las personas deben permitir que otras personas 
hablen entre el saludo y el comentario sobre el regalo
EXCLUSIÓN MUTUA
103
• Una instrucción atómica es aquella que se ejecuta 
como una unidad indivisible
• El lenguaje de programación y el hardware definen las 
instrucciones atómicas en las que se divide cada 
sentencia
x = x+1
LOAD R,x
ADD R,#1
STR R,x
Instrucciones Atómicas
Sentencia alto nivel
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
104
• Supongamos que dos procesos quieren usar una 
variable común para contar las acciones realizadas
• Si dos procesos quieren incrementar la misma 
variable existen intercalaciones de las instrucciones 
atómicas que producen errores en la cuenta
• Para el desarrollador sería muy interesante que el 
incremento de una variable fuese una instrucción 
atómica
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
105
•Tipos de instrucciones atómicas
 De grano fino
 Ofrecidas por el lenguaje de programación y el hardware al 
desarrollador
 Habitualmente se corresponden con las instrucciones máquina del 
procesador
 De grano grueso
 Conjunto de sentencias que ejecuta un proceso sin interferencias 
de otros procesos
 Los lenguajes de programación y el sistema hardware disponen de 
mecanismos para hacer que un grupo de sentencias se ejecuten 
como una instrucción atómica de grano grueso
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
106
• Una sección bajo Exclusión Mutua es una 
instrucción atómica de grano grueso
 Indivisible
 Ningún otro proceso puede interferir en el uso del recurso 
compartido
 Si dentro de la sección crítica se incrementa una variable, 
ningún otro proceso podrá interferir y no se producirán 
errores en la cuenta
 Divisible
 Pero es divisible en el sentido de que se pueden intercalar 
instrucciones de la sección no crítica de otros procesos
Exclusión Mutua
SINCRONIZACIÓN CON ESPERA ACTIVA
107
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de Corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
108
Propiedades de Corrección
• La programación concurrente es tan compleja 
que se han definido un conjunto de 
propiedades de corrección que todo programa 
concurrente debe cumplir para considerarse 
correcto
• Estas propiedades deben cumplirse en 
cualquier posible intercalación de las 
instrucciones atómicas
MEMORIA COMPARTIDA
109
Propiedades de Corrección
• Exclusión Mutua
 Hay que poner correctamente bajo exclusión 
mutua los recursos compartidos
 De no ser así, se pueden producir interferencias 
indeseadas entre los procesos que tengan como 
consecuencia la corrupción de los datos 
compartidos
 Cuando esto ocurre se dice que se ha producido una 
“condición de carrera” (race condition)
MEMORIA COMPARTIDA
110
Propiedades de Corrección
• Ausencia de Interbloqueos
 Dos procesos se bloquean mutuamente cuando se están esperando el 
uno al otro y no pueden continuar su ejecución mientras esperan
 Interbloqueo activo (livelock): 
 Cuando los procesos ejecutan instrucciones que no producen un 
avance real del programa, sólo sirven para sincronizarse entre sí
 Interbloqueo pasivo (deadlock)
 Cuando todos los procesos están bloqueados esperando 
indefinidamente
 Este interbloqueo sólo se produce con herramientas de sincronización 
(se verán más adelante)
MEMORIA COMPARTIDA
111
Propiedades de Corrección
• Ausencia de Retrasos Innecesarios
 Los procesos deben progresar en su ejecución si no 
hay otro proceso compitiendo con él para entrar en 
las secciones bajo exclusión mutua
 Generalmente las primitivas concurrentes 
garantizan que esto no ocurra. El desarrollador no 
tiene que preocuparse de ello.
MEMORIA COMPARTIDA
112
Propiedades de Corrección
• Ausencia de Inanición (starvation)
 Todo proceso que quiera acceder a un recurso 
compartido deberá poder hacerlo (en algún 
momento)
 Generalmente las primitivas concurrentes 
garantizan que esto no ocurra. El desarrollador no 
tiene que preocuparse de ello.
MEMORIA COMPARTIDA
113
Propiedades de Corrección
• Tipos de propiedades de corrección:
 De seguridad (safety): Si alguna de estas 
propiedades se incumple en alguna ocasión, el 
programa se comportará de forma errónea
 De Vida (liveness): Si alguna de estas propiedades 
se incumple en “alguna” ocasión, el programa se 
comportara de forma correcta pero será más lento 
de lo que podría ser y desaprovechará los recursos
MEMORIA COMPARTIDA
114
Propiedades de Corrección
• Propiedades de seguridad (safety)
 Exclusión Mutua
 Ausencia de Interbloqueo pasivo 
• Propiedades de Vida (liveness)
 Ausencia de Retrasos innecesarios
 Ausencia de inanición (starvation)
 Ausencia de interbloqueo activo (livelock)
MEMORIA COMPARTIDA
115
Propiedades de Corrección
• Justicia en el acceso a recursos compartidos 
(fariness):
 Espera FIFO: Si un proceso quiere acceder a un recurso, 
lo hará antes de que lo haga otro proceso que lo solicite 
después que él (cola del pan)
 Espera lineal: Si un proceso quiere acceder a un 
recurso, lo hará antes de que otro proceso lo haga más 
de una vez (se cuela una vez, pero no dos)
 Aleatorio: No se considera justa, pero es la más 
eficiente (se usa cuando no se necesita justicia)
MEMORIA COMPARTIDA
116
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
117
Memoria Compartida
• Espera Activa vs Herramientas de 
sincronización
 Problemas de la Espera Activa 
 Herramientas de sincronización
 Implementación de la Multiprogramación
 Algoritmos no bloqueantes
PROGRAMACIÓN CONCURRENTE
118
Problemas de la Espera Activa
• Se han estudiado técnicas básicas de sincronización de 
procesos en el modelo de Memoria Compartida con hilos y 
cerrojos usando únicamente variables compartidas para la 
sincronización
• Se denomina Espera Activa porque los procesos están 
ejecutando instrucciones (están activos) incluso cuando tienen 
que esperar para poder continuar su ejecución 
• También se la conoce como Busy waiting o spinning o polling 
(aunque este último término es más usado en entrada/salida)
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
while (!continuar);
119
Problemas de la Espera Activa
• La Espera Activa presenta los siguientes problemas
 Multiprogramación
 Los procesos que están esperando están malgastando el procesador 
que podría usarse por otros procesos que realmente estén realizando 
un trabajo útil
 Multiproceso
 Un procesador ejecutando instrucciones consume energía y por tanto 
disipa calor
 Si las instrucciones no son útiles, el procesador podría estar en reposo
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
La Espera Activa es muy ineficiente y 
NO se debe usar en programas 
concurrentes
120
Problemas de la Espera Activa
• Para solucionar los problemas de la espera activa, se 
desarrollaron herramientas de sincronización de 
procesos en los procesadores, lenguajes de programación 
y librerías
• Con estas herramientas, cuando un proceso no puede 
continuar ejecutando las sentencias se bloquea y deja de 
ejecutar sentencias hasta que otro proceso lo 
desbloquea cuando se cumplen las condiciones para que 
siga ejecutando
• Esto permite aprovechar de forma mucho mas adecuada 
los recursos (capacidad de cómputo, energía, …)
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
121
Memoria Compartida
• Espera Activa vs Herramientas de 
sincronización
 Problemas de la Espera Activa 
 Herramientas de sincronización
 Implementación
de la Multiprogramación
 Algoritmos no bloqueantes
PROGRAMACIÓN CONCURRENTE
122
Herramientas de sincronización
• Puesto que la espera activa en general es ineficiente, 
se han diseñado herramientas que permiten 
sincronizar procesos de forma más eficiente
• Existen algunas herramientas de bajo nivel de 
abstracción y otras de mayor nivel de abstracción 
dependiendo de las necesidades del desarrollador
• En general lo mejor es usar las herramientas de 
mayor nivel que permiten implementar los requisitos
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
123
Herramientas de sincronización
• En la programación funcional y en la programación 
orientada a objetos, la gran mayoría de los lenguajes de 
programación implementan los mismos conceptos
 Funcional: Funciones, listas, patrones…
 Orientación a Objetos: Clases, objetos, métodos, atributos…
• En la programación concurrente, incluso en lenguajes 
que implementan el mismo modelo, pueden existir 
diferencias sustanciales entre las herramientas que 
ofrecen
• Algunas herramientas básicas suelen estar disponibles en 
todos los lenguajes
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
124
Herramientas de sincronización
• Herramientas de sincronización de procesos en 
el modelo de Memoria Compartida
 Semáforos
 Monitores
 Regiones Críticas 
 Regiones Críticas Condicionales 
 Sucesos 
 Buzones
 Recursos
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
125
Memoria Compartida
• Espera Activa vs Herramientas de 
sincronización
 Problemas de la Espera Activa 
 Herramientas de sincronización
 Implementación de la Multiprogramación
 Algoritmos no bloqueantes
PROGRAMACIÓN CONCURRENTE
126
Implementación de la Multiprogramación
• Cuando se ejecuta un programa concurrente en 
multiprogramación, sólo un proceso puede estar 
ejecutándose en el procesador a la vez
• Para coordinar a todos los procesos que comparten el 
procesador
 Planificación (Scheduling)
 Despacho (Dispatching)
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
127
Implementación de la Multiprogramación
• Planificación (Scheduling) 
 Política que determina la asignación, en cada 
instante, de los procesos a los procesadores 
disponibles
 FIFO: El primero que llegó
 Lineal: No puede volver el mismo proceso hasta que 
no hayan ejecutado los demás
 Prioridades: El más prioritario
 Aleatoria
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
128
• Despacho (Dispatching)
 Configuración del procesador para que ejecute el proceso que ha 
determinado la planificación
 El proceso debe ejecutarse con las mismas condiciones en las que 
abandonó el procesador
 Para ello el sistema dispone de un Descriptor de Proceso (Process 
Control Block) por cada proceso que alberga toda la información 
necesaria
 Identificador del proceso (Process ID, PID)
 Estado
 Entorno, Memoria, …
Implementación de la Multiprogramación
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
129
• Para implementar las Herramientas de sincronización, un 
proceso en un sistema en multiprogramación puede estar 
en los siguientes estados
Bloqueado
Preparado Ejecución
Implementación de la Multiprogramación
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
130
• Un proceso estará en el estado bloqueado debido a 
diversos motivos:
 Está esperando por una operación de entrada/salida y no puede 
continuar su ejecución hasta que finalice la operación
 Ha ejecutado una instrucción de tipo sleep()
 Usa una herramienta de sincronización (por ejemplo un semáforo) y 
está esperando a que se cumpla cierta condición para volver a estar 
preparado
Bloqueado
Preparado Ejecución
Implementación de la Multiprogramación
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
131
Memoria Compartida
• Espera Activa vs Herramientas de 
sincronización
 Problemas de la Espera Activa 
 Herramientas de sincronización
 Implementación de la Multiprogramación
 Algoritmos no bloqueantes
PROGRAMACIÓN CONCURRENTE
132
Algoritmos no bloqueantes
• Cada vez que un proceso A intenta acceder a un recurso 
bajo exclusión mutua y ya hay otro proceso usando el 
recurso, el proceso A se tiene que bloquear
• Cuando hay muchos procesos intentando acceder al 
mismo recurso, se dice que tiene mucha competencia 
(contended)
• Cuando eso ocurre, ese recurso compartido se convierte 
en un cuello de botella e impide que el programa pueda 
aprovechar todos los recursos (escalar)
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
133
Algoritmos no bloqueantes
• Cuando muchos procesos intentan acceder a 
recursos compartidos bajo exclusión mutua se 
pueden producir demasiados bloqueos y 
desbloqueos de procesos y esto también puede ser 
demasiado ineficiente
• Para evitarlo se reduce al máximo el tamaño de la 
sección bajo exclusión mutua, para reducir la 
probabilidad de que la sección bajo EM esté ocupada
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
134
Algoritmos no bloqueantes
• Algoritmos no bloqueantes
 Otra técnica para evitar los bloqueos consiste en 
usar algoritmos que no ponen los recursos bajo EM
 Para evitar condiciones de carrera al acceder a los 
recursos compartidos, usan instrucciones atómicas 
hardware de lectura y escritura de variables 
(Comparse and Set) en bucles de espera activa
 A estos algoritmos se les llama: non-blocking 
algorithms y lock-free algorithms
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
135
Algoritmos no bloqueantes
• Estos algoritmos se consideran avanzados y no 
se van a estudiar en clase
•Muchas herramientas concurrentes de Java 
están implementadas internamente usando 
esta técnica avanzada
ESPERA ACTIVA VS HERRAMIENTAS DE SINCRONIZACIÓN
136
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
137
Introducción a los semáforos
• Dijkstra introdujo la primera 
herramienta de sincronización de 
procesos en 1968 y la denominó 
Semáforo
• Es una herramienta de 
sincronización de procesos de bajo 
nivel que permite implementar de 
forma sencilla la sincronización 
condicional y la exclusión mutua
MEMORIA COMPARTIDA
Dijkstra 
(1930-2002)
138
Introducción a los semáforos
• Un semáforo es una clase
• El estado interno está formado por un 
contador de permisos y un conjunto de 
procesos bloqueados
• Las operaciones permiten bloquear y 
desbloquear procesos dependiendo del 
número de permisos
MEMORIA COMPARTIDA
139
Introducción a los semáforos
• En la librería SimpleConcurrent los semáforos se 
representan como objetos de la clase 
SimpleSemaphore
• Métodos públicos
 public SimpleSemaphore(int permits)
 Constructor que inicializa el número de permisos iniciales del 
semáforo con el valor indicado
 void acquire() y void release()
 Métodos invocados por los procesos para sincronizarse entre sí
 Se ejecutan de forma atómica
 Su comportamiento depende del número de permisos
MEMORIA COMPARTIDA
140
Introducción a los semáforos
• void acquire() 
 El proceso que invoca este método intenta “adquirir” un permiso del 
semáforo. Si lo consigue, continúa la ejecución. Si no, queda bloqueado
 Internamente el semáforo tiene un contador de permisos (permits). 
 Si el número de permisos del semáforo es mayor que cero (permits>0), 
se decrementa una unidad el número de permisos y el proceso continúa 
su ejecución
 Si el número de permisos del semáforo es cero (permits=0), el proceso 
suspende su ejecución, pasa al estado bloqueado y se añade al conjunto 
de procesos bloqueados en el semáforo
 No es posible que un semáforo tenga un
número negativo de permisos
MEMORIA COMPARTIDA
permits = permits - 1
141
Introducción a los semáforos
• void release(); 
 El proceso que invoca este método “libera” un permiso 
previamente adquirido. Si otro proceso estaba esperando un 
permiso, lo consigue en la misma operación y se desbloquea.
 El hilo que ejecuta este método nunca queda bloqueado, siempre 
continúa la ejecución
 Si no existen procesos bloqueados en el semáforo, se incrementa 
el número de permisos.
 Si existen procesos bloqueados en el semáforo, se desbloquea 
aleatoriamente a uno cualquiera.
MEMORIA COMPARTIDA
permits = permits + 1
142
Introducción a los semáforos
• Se puede pensar en un semáforo como una caja llena de 
bolas
 El número de permisos representa el número de bolas
 acquire()
 El proceso que ejecuta acquire() tiene que coger una bola de la caja
 Si hay una o más bolas, coge una y continúa
 Si no hay bolas, se bloquea hasta que estén disponibles
 release() 
 Echa una nueva bola a la caja
 Si algún proceso estaba esperando bola, la coge y se desbloquea
 Si no había ningún proceso esperando, la bola se queda en la caja
MEMORIA COMPARTIDA
143
Introducción a los semáforos
MEMORIA COMPARTIDA
Operación que 
ejecuta un proceso
Antes Después
Permisos Procesos Bloqueados Permisos
Procesos 
Bloqueados
acquire() 3 Ninguno 2 Ninguno
acquire() 0 P1 0 P1,P2
release() 1 Ninguno 2 Ninguno
release() 0 Ninguno 1 Ninguno
release() 0 P1,P3 0 P1*
* Podría haberse desbloqueado cualquiera de los procesos
144
Introducción a los semáforos
• Diferentes nombres para las operaciones acquire y release
 Las operaciones de gestión del semáforo reciben diferentes nombres 
dependiendo del sistema operativo, lenguaje de programación y/o librería.
MEMORIA COMPARTIDA
acquire release Descripción
P V Los nombres que Dijkstra propuso originalmente a las 
operaciones en idioma holandés. V proviene de verhogen 
(incrementar) y P proviene de portmanteau prolaag (intentar 
reducir)
Down Up ALGOL 68, el kernel de Linux kernel y algunos libros de texto
Wait Signal PascalFC y algunos libros de texto
Pend Post
Procure Vacate Procure significa obtener y vacate desocupar
145
Introducción a los semáforos
• Tipos de Semáforos 
 Según el número de permisos
 Semáforos Binarios: Como máximo sólo pueden gestionar un permiso
 Semáforos Generales: Pueden gestionar cualquier número de 
permisos
 Según la política de desbloqueo de procesos
 FIFO (First In, First Out): Los procesos se desbloquean en orden de 
llegada
 Aleatorio: Los procesos se desbloquean aleatoriamente 
MEMORIA COMPARTIDA
Los semáforos SimpleSemaphore son 
generales aleatorios
146
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
147
Sincronización Condicional
• Se produce cuando un proceso debe esperar a que se 
cumpla una cierta condición para proseguir su ejecución
• Esta condición sólo puede ser activada por otro 
proceso
SINCRONIZACIÓN CON SEMÁFOROS
PA1 PA2
PB1 PB2
Diagrama de Precedencia
Proceso A
Proceso B
148
public class SincCond {
static volatile boolean continuar;
public static void a() {
print("PA1 ");
continuar = true;
print("PA2 ");
}
public static void b() {
print("PB1 ");
while (!continuar);
print("PB2 ");
}
public static void main(String[] args){
continuar = false;
createThread("a");
createThread("b");
startThreadsAndWait();
}
}
Sincronización
Condicional
• Implementación 
con Espera Activa
PA1 PA2
PB1 PB2
Proceso A
Proceso B
149
Sincronización
Condicional
public class SincCondSem {
static SimpleSemaphore continuar;
public static void a() {
print("PA1 ");
continuar.release();
print("PA2 ");
}
public static void b() {
print("PB1 ");
continuar.acquire();
print("PB2 ");
}
public static void main(String[] args){
continuar = new SimpleSemaphore(0);
createThread("a");
createThread("b");
startThreadsAndWait();
}
}
• Implementación 
con Semáforos
PA1 PA2
PB1 PB2
Proceso A
Proceso B
Download this code 
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/SincCondSem.java
150
150
Semáforos (Eficiente)Espera Activa (Ineficiente)
public class SincCond {
static volatile boolean continuar;
public static void a() {
print("PA1 ");
continuar = true;
print("PA2 ");
}
public static void b() {
print("PB1 ");
while (!continuar);
print("PB2 ");
}
public static 
void main(String[] args){
continuar = false;
createThread("a");
createThread("b");
startThreadsAndWait();
}
}
public class SincCondSem {
static SimpleSemaphore continuar;
public static void a() {
print("PA1 ");
continuar.release();
print("PA2 ");
}
public static void b() {
print("PB1 ");
continuar.acquire();
print("PB2 ");
}
public static 
void main(String[] args){
continuar = new SimpleSemaphore(0);
createThread("a");
createThread("b");
startThreadsAndWait();
}
}
151
Sincronización Condicional
• Comportamiento de los procesos con 
herramientas de sincronización (bloqueantes)
 Un proceso se bloquea a sí mismo
 Un proceso se bloquea a sí mismo si no puede 
proseguir su ejecución
 Un proceso nunca bloquea a otro proceso
 Un proceso desbloquea a otro
 Un proceso nunca se desbloquea a sí mismo
 Un proceso desbloquea a otro proceso cuando ese otro 
proceso puede proseguir su ejecución
SINCRONIZACIÓN CON SEMÁFOROS
152
Exclusión Mutua
• El problema de la 
Exclusión Mutua
 Se tienen dos o más 
procesos concurrentes, 
que ejecutan 
indefinidamente una 
secuencia de 
instrucciones dividida en 
dos secciones: sección 
bajo Exclusión Mutua y 
la sección sin EM
SINCRONIZACIÓN CON SEMÁFOROS
while(true) {
// Sección bajo EM
printlnI("P1_EM1");
printlnI("P1_EM2");
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
153
Exclusión 
Mutua
• La exclusión mutua 
con espera activa con 
SimpleConcurrent se 
implementa usando 
enterMutex() y 
exitMutex()
• Implementada con 
semáforos es mucho 
más eficiente
153
public class ExcMutuaSem {
public static void p() {
for (int i = 0; i < 5; i++) {
// Sección bajo EM
enterMutex();
printlnI("P1_EM1");
printlnI("P1_EM2");
exitMutex();
// Sección sin EM
printlnI("P1_1");
printlnI("P2_2");
}
}
public static void main(String[] args) {
createThreads(2,"p");
startThreadsAndWait();
}
}
154
Exclusión 
Mutua
• Para entrar en la sección 
bajo EM hay que coger 
una bola de la caja, y 
dejarla al salir para que la 
pueda coger el próximo 
proceso que quiera entrar
• Cuando el semáforo 
tiene 1 permiso 
(permits=1), la sección 
crítica está libre
• Cuando el semáforo no 
tiene permisos 
(permits=0), la sección 
crítica está ocupada por 
un proceso
154
public class ExcMutuaSem {
private static SimpleSemaphore em;
public static void p() {
for (int i = 0; i < 5; i++) {
// Sección bajo EM
em.acquire();
printlnI("P1_EM1");
printlnI("P1_EM2 ");
em.release();
// Sección sin EM
printlnI("P1_1");
printlnI("P1_2");
}
}
public static void main(String[] args) {
em = new SimpleSemaphore(1);
createThreads(2,"p");
startThreadsAndWait();
}
} Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ExcMutuaSem.java
155
Ejercicio 13A
• Una línea del metro está formada por varios tramos de vía 
que son recorridos secuencialmente en un único sentido por 
diferentes trenes
• Cada tren está representado por un hilo que realiza las 
siguientes operaciones:
SINCRONIZACIÓN CON SEMÁFOROS
public static void tren(int numTren) {
sleepRandom(500);
recorrerTramoA(numTren);
sleepRandom(500);
recorrerTramoB(numTren);
sleepRandom(500);
recorrerTramoC(numTren);
}
156
Ejercicio 13A
SINCRONIZACIÓN CON SEMÁFOROS
public class Metro {
private static final int NUM_TRENES = 5;
 public static void tren(int numTren) {
...
}
private static void recorrerTramoA(int numTren) {
printlnI("Entra TA T" + numTren);
sleepRandom(500);
printlnI("Sale TA T" + numTren);
}
 public static void main(String args[]) {
for (int i = 0; i < NUM_TRENES; i++) {
createThread("tren", i);
}
startThreadsAndWait();
}
}
157
Ejercicio 13A
• El funcionamiento actual es:
 Varios trenes pueden estar en el mismo tramo a la vez
 Unos trenes pueden adelantar a otros
• Se quiere el siguiente funcionamiento:
 Cada tramo sólo pueda estar ocupado por un tren en cada 
instante
 Los trenes nunca pueden adelantarse unos a otros
• Para ello únicamente hay que añadir herramientas 
de sincronización de procesos (semáforos)
SINCRONIZACIÓN CON SEMÁFOROS
158
Ejercicio 13B
• Se quiere hacer el código genérico para que el 
número de tramos se pueda especificar con 
una constante
SINCRONIZACIÓN CON SEMÁFOROS
159
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
160
Sincronización Avanzada
• Todo programa concurrente se puede 
implementar con sincronizaciones 
condicionales y exclusiones mutuas
• En algunas ocasiones este tipo de 
sincronizaciones son muy básicas, de muy 
bajo nivel
• Existen otras formas de sincronización más 
avanzadas, de más alto nivel
MEMORIA COMPARTIDA
161
Sincronización Avanzada
• Veremos varios ejemplos de sincronizaciones 
avanzadas:
 Exclusión Mutua Generalizada: En la zona bajo 
exclusión mutua puede haber varios procesos (no 
sólo 1)
 Comunicación con Buffer: Varios procesos se 
comunican entre sí usando una estructura de datos 
intermedia para no bloquearse.
MEMORIA COMPARTIDA
162
Exclusión Mutua Generalizada
• Tipos de Exclusión Mutua generalizada:
 Exclusión Mutua con varios procesos: Sin ninguna 
restricción salvo el número de ellos
 Lectores – Escritores: Pueden ejecutar 
instrucciones múltiples procesos lectores o un único 
proceso escritor (se verá como ejercicio)
 Específicos del problema: Cada problema puede 
tener sus propias restricciones
SINCRONIZACIÓN AVANZADA
163
Exclusión Mutua Generalizada
• Exclusión Mutua con varios procesos:
 Se tiene cuando más de un proceso puede ejecutar 
la sección bajo exclusión mutua
 Si el número de procesos es K, se implementa con 
un semáforo con un valor inicial de K
SINCRONIZACIÓN AVANZADA
164
Exclusión Mutua
con varios proc
public class ExcMutuaGenSem {
private static SimpleSemaphore em;
public static void p() {
for (int i = 0; i < 5; i++) {
// Sección bajo EM
em.acquire();
printlnI("P_EM1");
printlnI("P_EM2");
em.release();
// Sección sin EM
printlnI("P_1");
printlnI("P_2");
}
}
public static void main(String[] args) {
em = new SimpleSemaphore(3);
createThreads(5,"p");
startThreadsAndWait();
}
}
Para entrar en la 
sección bajo EM 
hay que coger una 
bola de la caja, y 
dejarla al salir para 
que la pueda coger 
otro proceso
El contador del 
semáforo indica los 
huecos libres de la 
sección crítica
165
Comunicación con Buffer
• En los ejercicios de productores y consumidores los procesos 
se comunicaban con una variable de tipo primitivo
• Para adaptar las velocidades diferentes de los productores y 
consumidores y que no haya esperas innecesarias se utilizan 
buffers para almacenar temporalmente información
• Un buffer permite que se pueda ir insertando información 
aunque no esté preparado el proceso encargado de consumirla
• Los buffers se usan mucho en informática:
 Sistemas operativos: teclado, red, escritura en disco, etc.
 Aplicaciones distribuidas: Comunicación entre diferentes nodos de la 
red
SINCRONIZACIÓN AVANZADA
166
Productores Consumidores
• Para estudiar la comunicación con buffers 
implementaremos el “Problema de los 
Productores y los Consumidores”
• Procesos
 Los Productores son procesos que generan datos
 Los Consumidores son procesos que consumen los 
datos en el orden en que se generan
COMUNICACIÓN CON BUFFER
167
Productores Consumidores
• Restricciones
 Cada productor genera un único dato cada vez
 Un consumidor consume un dato generado por el 
productor cada vez
 Todos los productos se consumen
 Si hay varios consumidores, ningún producto se 
consume dos veces
COMUNICACIÓN CON BUFFER
168
Productores Consumidores
• Comunicación
 Se utilizará un array o una lista para almacenar 
temporalmente los datos producidos antes de ser 
consumidos
• Sincronización
 Los consumidores deben de bloquearse cuando no 
tengan datos que consumir (array vacío)
 Los productores deben bloquearse cuando no 
puedan insertar más datos en el buffer (array lleno)
COMUNICACIÓN CON BUFFER
169
Productores 
Consumidores
169
public class ProdConsBufferMal {
 //Atributos y métodos del buffer
...
//Hilos y main
public static void productor() {
for(int i=0; i<20; i++){
sleepRandom(500);
insertarBuffer(i);
}
}
public static void consumidor() {
for(int i=0; i<20; i++){
String dato = sacarBuffer();
sleepRandom(500);
print(dato);
}
}
public static void main(String[] args) {
createThreads(5, "productor");
createThreads(3, "consumidor");
startThreadsAndWait();
}
}
Esquema de la 
aplicación
Hay 5 hilos 
productores que 
generan 20 
productos y finalizan
Hay 3 hilos 
consumidores que 
consumen 20 
productos y finalizan
Download this code
https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9
https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9
https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9
https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9
https://gist.github.com/micaelgallego/3dede7ed06cc721a9bf9
170
Productores 
Consumidores
class ProdConsBufferMal {
 //Atributos y métodos del buffer
int[] datos = new int[10];
int posInser, posSacar = 0;
public static void insertarBuffer(int dato) {
datos[posInser] = dato;
posInser = (posInser+1) % 10;
}
public static int sacarBuffer() {
int dato = datos[posSacar];
posSacar = (posSacar+1) % 10;
return dato;
}
//Hilos y main
...
}
Implementación 
del buffer con un 
array circular
*
*
*
*
posInser
posSacar
0
1 2
3
4
56
7
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBufferMal.java
171
Productores Consumidores
• El programa no es correcto porque falta 
incorporar los puntos de sincronización 
 Sincronización Condicional
 Un productor se bloqueará antes de insertar un dato 
si el buffer está lleno
 Un consumidor se bloqueará antes de leer un dato si 
el buffer está vacío
 Exclusión Mutua
 Las variables de control del buffer deben estar bajo 
exclusión mutua
COMUNICACIÓN CON BUFFER
172
Productores 
Consumidores
172
172
public class ProdConsBuffer {
 //Atributos y métodos del buffer
 SimpleSemaphore nHuecos = new SimpleSemaphore(10);
 SimpleSemaphore nProductos = new SimpleSemaphore(0);
 SimpleSemaphore emPosInser = new SimpleSemaphore(1);
 SimpleSemaphore emPosSacar = new SimpleSemaphore(1);
int[] datos = new int[10];
int posInser, posSacar = 0;
public static void insertarBuffer(int dato) {
nHuecos.acquire();
emPosInser.acquire();
datos[posInser] = dato;
posInser = (posInser+1) % 10;
emPosInser.release();
nProductos.release();
 }
public static int sacarBuffer() {
nProductos.acquire();
emPosSacar.acquire();
int dato = datos[posSacar];
posSacar = (posSacar+1) % 10;
emPosSacar.release();
nHuecos.release();
return dato;
}
//Hilos y main...
}
Cada 
contador se 
protege bajo 
su propio 
semáforo de 
exclusión 
mutua
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java
173
public class ProdConsBuffer {
 //Atributos y métodos del buffer
 SimpleSemaphore nHuecos = new SimpleSemaphore(10);
 SimpleSemaphore nProductos = new SimpleSemaphore(0);
 SimpleSemaphore emPosInser = new SimpleSemaphore(1);
 SimpleSemaphore emPosSacar = new SimpleSemaphore(1);
int[] datos = new int[10];
int posInser, posSacar = 0;
public static void insertarBuffer(int dato) {
nHuecos.acquire();
emPosInser.acquire();
datos[posInser] = dato;
posInser = (posInser+1) % 10;
emPosInser.release();
nProductos.release();
 }
public static int sacarBuffer() {
nProductos.acquire();
emPosSacar.acquire();
int dato = datos[posSacar];
posSacar = (posSacar+1) % 10;
emPosSacar.release();
nHuecos.release();
return dato;
}
//Hilos y main...
}
El proceso que 
quiere sacar 
queda 
bloqueado si 
no hay 
productos
Hay tantos 
permisos 
como 
productos
Productores 
Consumidores
173Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java
174
public class ProdConsBuffer {
 //Atributos y métodos del buffer
 SimpleSemaphore nHuecos = new SimpleSemaphore(10);
 SimpleSemaphore nProductos = new SimpleSemaphore(0);
 SimpleSemaphore emPosInser = new SimpleSemaphore(1);
 SimpleSemaphore emPosSacar = new SimpleSemaphore(1);
int[] datos = new int[10];
int posInser, posSacar = 0;
public static void insertarBuffer(int dato) {
nHuecos.acquire();
emPosInser.acquire();
datos[posInser] = dato;
posInser = (posInser+1) % 10;
emPosInser.release();
nProductos.release();
 }
public static int sacarBuffer() {
nProductos.acquire();
emPosSacar.acquire();
int dato = datos[posSacar];
posSacar = (posSacar+1) % 10;
emPosSacar.release();
nHuecos.release();
return dato;
}
//Hilos y main...
}
Productores 
Consumidores
El proceso que 
quiere 
insertar 
queda 
bloqueado si 
no hay huecos
Hay tantos 
permisos 
como huecos
174Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejemplo/ProdConsBuffer.java
175
Memoria Compartida
• SimpleConcurrent
• Intercalación de instrucciones: Indeterminismo
• Variables Compartidas
• Sincronización con Espera Activa
• Propiedades de corrección
• Espera Activa vs Herramientas de sincronización
• Introducción a los Semáforos
• Sincronización con Semáforos
• Sincronización avanzada
• Conclusiones
PROGRAMACIÓN CONCURRENTE
176
Conclusiones
• Espera activa vs Herramientas de sincronización
 La espera activa es una técnica que no debe usarse nunca 
para desarrollar programas concurrentes porque es 
ineficiente
 Deben usarse herramientas de sincronización bloqueantes 
para aprovechar mejor los recursos de cómputo.
 En general es mejor usar las herramientas de alto nivel que 
proporcionan las tecnologías de desarrollo (lenguajes + 
librerías) que pueden estar implementadas con algoritmos 
no bloqueantes
MEMORIA COMPARTIDA
177
Conclusiones
• Ventajas de los Semáforos
 Son muy fáciles de entender
 Permiten sincronizar procesos de forma sencilla y 
eficiente
 Están presentes en la mayoría de las tencologías de 
desarrollo (lenguajes+librerías)
MEMORIA COMPARTIDA
178
Conclusiones
• Desventajas de los Semáforos
 Son primitivas de muy bajo nivel
 Omitir una llamada a release() puede provocar 
interbloqueo
 Omitir una llamada a acquire() puede provocar 
condiciones de carreara (por no estar bajo exclusión 
mutua)
 No estructuran el código del programa, lo que hace que los 
códigos sean difíciles de mantener y de eliminar los errores 
que se produzcan
MEMORIA COMPARTIDA
179
Conclusiones
• Para solventar los problemas de los semáforos, 
existen otras herramientas de sincronización de 
procesos con mayor nivel de abstracción
 Monitores (presentes en Java)
 Regiones Críticas 
 Regiones Críticas Condicionales 
 Sucesos 
 Buzones
 Recursos
MEMORIA COMPARTIDA
180
Conclusiones
• Programación concurrente con memoria 
compartida: Difícil de implementar programas 
concurrentes y difícil de corregir errores
 Condiciones de carrera: Por no poner bajo EM los recursos 
compartidos.
 Interbloqueos: Cuando dos procesos se bloquean 
esperando que el otro los desbloquee.
 Aprovechamiento de recursos: Cuando el programa no es 
lo suficientemente concurrente y no aprovecha todos los 
procesadores disponibles o emplea demasiado tiempo 
sincronizando los procesos.
MEMORIA COMPARTIDA
181
Conclusiones
• Popularización de otros modelos de 
programación concurrente:
 Paso de mensajes:
 Actores: Librerías para Java (Akka, Vert.x, Reactor)
 CSP: Lenguaje de programación Go!
 Modelos asíncronos (sin bloqueo explícito) :
 JavaScript, Vert.x, Akka…
MEMORIA COMPARTIDA
182
14. Sincronización de Barrera
• La Sincronización de Barrera (Barrier) es una 
sincronización condicional en la que los procesos tienen 
que esperar a que el resto de procesos lleguen al mismo 
punto para poder continuar su ejecución
• Vamos a estudiar este tipo de sincronización con los 
siguientes requisitos
 Programa con N procesos
 Cada proceso escribe la letra A y luego la B
 Los procesos tienen que esperar que todos hayan escrito la letra A 
antes de escribir la B
EJERCICIOS
183
Sincronización 
de Barrera
public class Ejer14_SincBarrera_Mal1 {
 private static final int NPROCESOS = 3;
 private static volatile int nProcesos;
 private static SimpleSemaphore sb;
 
 public static void proceso() {
 print("A");
 nProcesos++;
 if (nProcesos < NPROCESOS) {
 sb.acquire();
 } else {
 for (int i=0; i<NPROCESOS - 1; i++) {
 sb.release();
 }
 }
 print("B");
 }
 public static void main(String[] args) {
 nProcesos = 0;
 sb = new SimpleSemaphore(0);
 createThreads(NPROCESOS, "proceso");
 startThreadsAndWait();
 }
}
1ª Aproximación
Incorrecta
Puede provocar 
interbloqueo 
(deadlock) porque 
nProcesos no 
está bajo exclusión 
mutua y se pueden 
producir errores al 
contar
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera_Mal1.java
184
private static final int NPROCESOS = 3;
private static volatile int nProcesos;
private static SimpleSemaphore sb;
private static SimpleSemaphore emNProcesos;
public static void proceso() {
 print("A");
 emNProcesos.acquire();
 nProcesos++;
 emNProcesos.release();
 if (nProcesos < NPROCESOS) {
 sb.acquire();
 } else {
 for (int i=0; i<NPROCESOS - 1; i++) {
 sb.release();
 }
 }
 print("B");
 }
 public static void main(String[] args) {
 nProcesos = 0;
 sb = new SimpleSemaphore(0);
 emNProcesos = new SimpleSemaphore(1);
 createThreads(NPROCESOS, "proceso");
 startThreadsAndWait();
 }
}
2ª Aproximación
Incorrecta
Si se deja la consulta 
del contador fuera 
de la Exclusión 
Mutua, puede 
ocurrir que dos 
procesos hagan 
release() 
Sincronización 
de Barrera
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera_Mal2.java
185
public class Ejer14_SincBarrera {
private static final int NPROCESOS = 3;
private static volatile int nProcesos;
private static SimpleSemaphore sb;
private static SimpleSemaphore emNProcesos;
public static void proceso() {
print("A");
emNProcesos.acquire();
nProcesos++;
if (nProcesos < NPROCESOS) {
emNProcesos.release();
sb.acquire();
} else {
emNProcesos.release();
for (int i=0; i< NPROCESOS-1; i++) {
sb.release();
}
}
print("B");
}
public static void main(String[] args) {
nProcesos = 0;
sb = new SimpleSemaphore(0);
emNProcesos = new SimpleSemaphore(1);
createThreads(NPROCESOS, "proceso");
startThreadsAndWait();
}
}
Solución Correcta
Si el proceso no es el 
último, libera la EM y se 
bloquea
Si es el último, sale de la 
EM y desbloquea a los 
demás procesos
Sincronización 
de Barrera
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarrera.java
186
15. Sincronización de Barrera Cíclica
• Se pide implementar un programa concurrente con las 
siguientes características:
 El programa tendrá 4 procesos que escriben una única letra 
indefinidamente. 
 Un proceso escribirá la letra A, otra la B, otra la C y otro la D.
 Cada proceso escribe su letra y espera a que los demás hayan escrito 
también su letra para poder continuar.
 Uno de los procesos tiene que escribir un guión – después de que todos 
hayan escrito su letra y antes de que empiecen a escribirlas de nuevo.
• La salida por pantalla del programa sería:
 ACDB-BACD-DCBA-ABCD-BCAD …
EJERCICIOS
187
public static void procesoA() {
 while(true){
 print("A");
 sincronizacion();
 }
}
public static void sincronizacion(){
 emNProcesos.acquire();
 nProcesos++;
 if (nProcesos < 4) {
 emNProcesos.release();
 sb.acquire();
 } else {
 println("-");
 nProcesos = 0;
 emNProcesos.release();
 for (int i = 0; i < 3; i++) {
 //sleepRandom(500); Condición carrera
 sb.release();
 }
 }
}
Sincronización 
de Barrera Cíclica
1ª Aproximación
Incorrecta
Un proceso puede ser 
desbloqueado, mostrar su 
letra, volver a entrar en 
sincronizacion() ejectuar 
sb.acquire(), ser 
desbloqueado de nuevo (con 
un permiso que no estaba 
destinado para él) y volver a 
escribir su letra. Todo ello 
antes de que todos lo procesos 
de la primera iteración se 
hayan desbloqueado
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl_Mal.java
188
public static void procesoA() {
 while(true){
 print("A");
 sincronizacion();
 }
}
public static void sincronizacion(){
 emNProcesos.acquire();
 nProcesos++;
 if (nProcesos < 4) {
 emNProcesos.release();
 //sleepRandom(500); Condición carrera
 sb.acquire();
 } else {
 println("-");
 nProcesos = 0;
 for (int i = 0; i < 3; i++) {
 sb.release();
 }
 emNProcesos.release();
 }
}
Sincronización 
de Barrera Cíclica
2ª Aproximación
Incorrecta
Se protege la entrada de los 
procesos a la segunda 
iteración hasta que todos los 
de la primera han sido 
desbloqueados.
El problema es que no 
podemos garantizar que han 
llegado a bloquearse en “sb” 
antes de liberar la exclusión 
mutua.
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl_Mal2.java
189
public static void procesoA() {
 while(true){
 print("A");
 sincronizacion();
 }
}
public static void sincronizacion() {
 emNProcesos.acquire();
 nProcesos++;
 if (nProcesos < 4) {
 emNProcesos.release();
 sleepRandom(500);
 sb.acquire();
 desbloqueo.release();
 } else {
 println("-");
 nProcesos = 0;
 sb.release(3);
 desbloqueo.acquire(3);
 emNProcesos.release();
 }
}
Sincronización 
de Barrera Cíclica
Solución
Obligando a que los procesos 
desbloqueados señalizen que 
les ha dado tiempo a 
bloquearse y desbloquearse. 
De esta forma se garantiza 
que un proceso no se adelanta 
a los demás
El nuevo semáforo 
desbloqueo se inicializa a 0 
porque es de sincronización 
condicional
Download this code
https://github.com/codeurjc/concurrencia-tema2/blob/master/src/main/java/ejercicio/Ejer14_SincBarreraCicl.java
190
16. Descarga de Ficheros
• Se desea ampliar el ejercicio de descarga de 
ficheros para descargar 10 ficheros.
• Cuando los procesos hayan terminado de 
descargar un fichero, se esperen a que el 
último proceso muestre por pantalla el fichero 
para comenzar a descargar un nuevo fichero
EJERCICIOS
191
17. Filósofos Comilones
• 5 Filósofos que piensan 
libremente y comen en una 
mesa con 5 platos 
• Cada plato está asignado a un 
filósofo
• Los platos se reponen 
continuamente 
• Hay 5 tenedores, uno entre 
cada par de platos adyacentes 
EJERCICIOS
F3
F2
F1F5
F4
T4 T3
T2
T1
T5
192
• Procesos (Filósofos)
 El filósofo inicialmente piensa 
 Se sienta delante de su plato y 
toma de uno en uno los 
tenedores situados a ambos 
lados de su plato 
 Come
 Cuando finaliza, deja los dos 
tenedores en su posición 
original
 Todo filósofo que come, en 
algún momento se harta y 
termina
17. Filósofos Comilones
EJERCICIOS
public static void 
 filosofo(int numFilosofo) {
 while (true) {
 printlnI("Pensar");
 // Obtener tenedores
 printlnI("Comer");
 // Liberar tenedores
 }
}
193
17. Filósofos Comilones
• Restricciones
 Un filósofo sólo puede comer cuando tenga los dos tenedores
 Los tenedores se cogen y se dejan de uno en uno
 Dos filósofos no pueden tener el mismo tenedor simultáneamente 
(EM de acceso al tenedor)
 Si varios filósofos tratan de comer al mismo tiempo, uno de ellos 
debe conseguirlo (Ausencia Interbloqueo)
 Si un filósofo desea comer y tiene competencia, en algún 
momento lo deberá poder hacer (Ausencia de Inanición)
 En ausencia de competencia, un filósofo que quiera comer deberá 
hacerlo sin retrasos innecesarios (Ausencia retrasos innecesarios)
EJERCICIOS
194
19. Lectores Escritores
• Acceso Combinado (Concurrente-
Exclusivo) a variables 
compartidas
• Dos tipos de procesos que 
comparten una misma Base de 
Datos: los Lectores y los Escritores
• Los Lectores pueden leer registros 
de la BD
• Los Escritores pueden leer y 
escribir los registros de la BD
EJERCICIOS
B.D
Escritores
Lectores
Leer
Escribir
Leer
195
19. Lectores Escritores
• Restricciones
 Cualquier número de lectores puede acceder a la vez a la BD, si no 
hay escritores accediendo
 El acceso a la BD de los escritores es exclusivo. Mientras haya algún 
lector leyendo, ningún escritor puede acceder a la BD, pero otros 
lectores sí.
 Se puede tener varios escritores trabajando, aunque estos se 
deberán sincronizar para que la escritura se lleve a cabo de uno en 
uno
 Se da prioridad a los escritores. Ningún lector puede acceder a la BD 
cuando haya escritores que desean hacerlo (Inanición de Lectores)
EJERCICIOS
196
19. Lectores Escritores
• Procesos
EJERCICIOS
public static void lector() {
 while(true){
 inicioLectura();
 println("Leer datos");
 finLectura();
 println("Procesar datos");
 }
}
public static void escritor() 
{
 while (true) {
 println("Generar datos");
 inicioEscritura();
 println("Escribir datos");
 finEscritura();
 }
}
		Slide 1
		Concurrencia con Memoria Compartida
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		SimpleConcurrent
		Concurrencia con Memoria Compartida
		Intercalación de instrucciones: Indeterminismo
		El orden de las instrucciones
		El orden de las instrucciones
		El orden de las instrucciones
		El orden de las instrucciones
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Intercalación (Interleaving)
Intercalación (Interleaving)
		Intercalación (Interleaving)
		Intercalación (Interleaving)
		Intercalación (Interleaving)
		Indeterminismo
		Indeterminismo
		Indeterminismo
		Indeterminismo
		Indeterminismo
		Indeterminismo
		Indeterminismo
		Concurrencia con Memoria Compartida
		Variables Compartidas
		Variables Compartidas
		Variables Compartidas
		Variables Compartidas
		Concurrencia con Memoria Compartida
		Concurrencia con Memoria Compartida
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Relaciones entre procesos
		Concurrencia con Memoria Compartida
		Sincronización Condicional
		Sincronización Condicional
		Sincronización Condicional
		Sincronización Condicional
		Sincronización Condicional
		Sincronización Condicional
		Ejercicio 1 – Productor Consumidor
		Ejercicio 2 – Productor Consumidor N
		Ejercicio 3 – Cliente Servidor 1 Petición
		Ejercicio 4 – Cliente Servidor N Peticiones
		Slide 71
		Concurrencia con Memoria Compartida
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Primer intento
		Primer intento
		Slide 82
		Primer intento
		Primer intento
		Segundo intento
		Segundo intento
		Slide 87
		Segundo intento
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Ejercicio 5 - Museo
		Ejercicio 5 - Museo
		Ejercicio 6 – Museo con infinitas personas
		Ejercicio 7 – Museo con regalo
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Concurrencia con Memoria Compartida
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Propiedades de Corrección
		Concurrencia con Memoria Compartida
		Concurrencia con Memoria Compartida
		Problemas de la Espera Activa
		Problemas de la Espera Activa
		Problemas de la Espera Activa
		Concurrencia con Memoria Compartida
		Herramientas de sincronización
		Herramientas de sincronización
		Herramientas de sincronización
		Concurrencia con Memoria Compartida
		Implementación de la Multiprogramación
		Implementación de la Multiprogramación
		Implementación de la Multiprogramación
		Implementación de la Multiprogramación
		Implementación de la Multiprogramación
		Concurrencia con Memoria Compartida
		Algoritmos no bloqueantes
		Algoritmos no bloqueantes
		Algoritmos no bloqueantes
		Algoritmos no bloqueantes
		Concurrencia con Memoria Compartida
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Introducción a los semáforos
		Concurrencia con Memoria Compartida
		Sincronización Condicional
		Sincronización Condicional
		Sincronización Condicional
		Slide 150
		Sincronización Condicional
		Exclusión Mutua
		Exclusión Mutua
		Exclusión Mutua
		Ejercicio 13A
		Ejercicio 13
		Ejercicio 13A
		Ejercicio 13B
		Concurrencia con Memoria Compartida
		Sincronización Avanzada
		Sincronización Avanzada
		Exclusión Mutua Generalizada
		Exclusión Mutua Generalizada
		Exclusión Mutua con varios proc
		Comunicación con Buffer
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Productores Consumidores
		Concurrencia con Memoria Compartida
		Conclusiones
		Conclusiones
		Conclusiones
		Conclusiones
		Conclusiones
		Conclusiones
		14. Sincronización de Barrera
		Sincronización de Barrera
		Sincronización de Barrera
		Slide 185
		15. Sincronización de Barrera Cíclica
		Slide 187
		Slide 188
		Slide 189
		16. Descarga de Ficheros
		17. Filósofos Comilones
		17. Filósofos Comilones
		17. Filósofos Comilones
		19. Lectores Escritores
		19. Lectores Escritores
		19. Lectores Escritores
__MACOSX/Academia/Tema2/._Tema 2. Memoria compartida.pdf
Academia/Tema2/Tema 2.1. Ejercicios.pdf
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
1 
 
 
 
Ejercicios Tema 2. Parte 1 
Concurrencia con Memoria Compartida 
Soluciones 
Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la 
sincronización con memoria compartida. 
Ejercicio 1. Productor Consumidor 
Se desea implementar un programa concurrente con un proceso que produce información (productor) y 
otro proceso que hace uso de esa información (consumidor). El proceso productor genera un número 
aleatorio y termina. El proceso consumidor muestra por pantalla el número generado y termina. 
Ejercicio 2. Productor Consumidor Infinito 
Se desea ampliar el programa del Ejercicio 1 de forma que el proceso productor esté constantemente 
produciendo números consecutivos. El proceso consumidor estará constantemente consumiendo los 
productos. No se puede quedar ningún producto sin consumir. No se puede consumir dos veces el mismo 
producto. 
Ejercicio 3. Cliente Servidor 1 Petición 
Programa formado por un proceso servidor y otro proceso cliente. El proceso cliente hace una petición al 
proceso servidor (en forma de número aleatorio) y espera su respuesta, cuando la recibe, la procesa. El 
proceso servidor no hace nada hasta que recibe una petición, momento en el que suma 1 al número 
enviado en la petición y contesta con ese valor. El proceso cliente procesa la respuesta mostrándola por 
pantalla. 
Ejercicio 4. Cliente Servidor N Peticiones 
Se desea ampliar el programa anterior de forma que el proceso cliente esté constantemente haciendo 
peticiones y el proceso servidor atendiendo a las mismas. 
Ejercicio 5. Museo 
Existen 3 personas en el mundo, 1 museo, y sólo cabe una persona dentro del museo. Las personas realizan 
cuatro acciones dentro del museo: 
 Cuando entran al museo saludan: “hola!” 
 Cuando ven el museo se sorprenden: “qué bonito!” y “alucinante!” 
 Cuando se van del museo se despiden: “adios” 
 Cuando salen del museo se van a dar un “paseo” 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
2 
 
Después del paseo, les ha gustado tanto que vuelven a entrar. Se pide: 
 Número de métodos diferentes que ejecutarán los procesos 
 Número de procesos del programa concurrente y de qué tipo son 
 Escribir lo que hace cada proceso 
 Identificar los recursos compartidos 
 Identificar la sección o secciones bajo exclusión mutua 
 Escribir el programa completo 
Ejercicio 6. Museo con infinitas personas 
Considerar que caben infinitas personas dentro del museo. Cada persona al entrar tiene que saludar 
diciendo cuántas personas hay en el museo: “hola, somos 3”. Al despedirse tiene que decir el número de 
personas que quedan tras irse: “adiós a los 2”. 
Ejercicio 7. Museo con regalo 
Para incentivar las visitas, cuando una persona entre en el museo estando vacío, será obsequiado con un 
regalo. Las personas, después de saludar, dicen si les han dado un regalo (“Tengo regalo”) o si no (“No 
tengo regalo”). Las personas deben permitir que otras personas saluden entre su saludo y el comentario 
sobre el regalo. 
Ejercicio 8. Preguntas Cortas 
8.1) Dado el programa siguiente ¿podría darse el caso de que uno o ninguno de los dos procesos finalizase? 
Razona la respuesta. 
package ejercicio; 
 
import static
es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer8_1 { 
 
 static volatile boolean fin = false; 
 
 public static void proceso() { 
 int i = 0; // I1 
 while (!fin) { // I2 
 i++; // I3 
 if (i == 3) { // I4 
 fin = true; // I5 
 } else { 
 fin = false; // I6 
 } 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(2, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
3 
 
8.2) Utilizando la espera activa se pretende resolver el problema de sincronización condicional de dos 
procesos concurrentes cuyo ciclo de vida es el siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer8_2 { 
 
 static volatile boolean flag = false; 
 
 public static void p1(){ 
 
 while(true){ 
 print("A"); 
 flag = true; 
 print("B"); 
 } 
 } 
 
 public static void p2(){ 
 while(true){ 
 print("C"); 
 while(!flag); 
 print("D"); 
 } 
 } 
 
 public static void main(String[] args) { 
 createThread("p1"); 
 createThread("p2"); 
 startThreadsAndWait(); 
 } 
} 
a) Después de la primera sincronización ¿se sincronizarán correctamente ambos procesos? Razone la 
respuesta. 
b) Qué tipo de interacción se produce entre los procesos P1 y P2? ¿En qué estados pueden estar los 
procesos P1 y P2? 
8.3) ¿Por qué razón el acceso (lecturas y/o escrituras) a variables globales compartidas por varios procesos 
debe realizarse bajo exclusión mutua en el modelo de variables compartidas? 
8.4) ¿En qué consiste el problema de la exclusión mutua? ¿Cuáles son los requisitos de su solución? 
8.5) Dibuje el diagrama de estados de un proceso 
8.6) ¿Qué diferencia hay entre los estados de preparado y de ejecución de un proceso? 
8.7) Defina en qué consisten las propiedades de seguridad de un programa concurrente. Cite dos 
propiedades de este tipo. 
8.8) ¿Qué es un interbloqueo de procesos? 
8.9) ¿Cuáles son las dos actividades principales necesarias para gestionar la ejecución de procesos 
concurrentes en multiprogramación? 
8.10) En la gestión de procesos concurrentes, explique la diferencia entre planificación y despacho. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
4 
 
Ejercicio 9. Downloader 
a) Se quiere implementar una aplicación para descargar ficheros. La aplicación debe tener la capacidad de 
descargar un único fichero, pero debe ser capaz de descargar varios fragmentos del fichero de manera 
concurrente para aprovechar de forma más eficiente la red. 
Para simplificar la aplicación, consideramos que un fichero se representa en memoria como un array de 
enteros. Internamente, la aplicación dispone de una serie de procesos que van descargando los diferentes 
fragmentos del fichero (posiciones del array). Los procesos están ejecutando tres acciones: primero se 
determina el siguiente fragmento a descargar, a continuación se descarga ese fragmento, y por último se 
guarda el fragmento descargado en el array que representa el fichero. 
La solución se puede implementar con espera activa o con semáforos. 
La descarga de los distintos fragmentos se simulará con el método: 
private static int descargaDatos(int numFragmento) { 
 sleepRandom(1000); 
 return numFragmento * 2; 
} 
Cuando se ha terminado de descargar completamente el fichero, se muestra por pantalla y el programa 
termina. Para mostrar el fichero por pantalla se utilizará el siguiente método: 
private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
} 
A continuación se presenta un esqueleto de la aplicación de descargas: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer_9A_Downloader_Plantilla { 
 
 private static final int N_FRAGMENTOS = 10; 
 private static final int N_HILOS = 3; 
 
 private static volatile int[] fichero = new int[N_FRAGMENTOS]; 
 
 //Add the attributes you need 
 
 private static int descargaDatos(int numFragmento) { 
 sleepRandom(1000); 
 return numFragmento * 2; 
 } 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
5 
 
 private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
 } 
 
 public static void downloader() { 
 
 // Mientras hay fragmentos que descargar... 
 //Descargar los datos del siguiente fragmento 
 //Almacenar los datos en el array 
 } 
 
 public static void main(String[] args) { 
 
 createThreads(N_HILOS, "downloader"); 
 
 startThreadsAndWait(); 
 
 mostrarFichero(); 
 } 
} 
b) En la plantilla anterior, la impresión del fichero se realiza en el hilo principal. Se pide implementar el 
mismo programa de descarga de ficheros pero esta vez la impresión será realizada por el último proceso 
que guarde un fragmento descargado en el fichero. 
 
Ejercicio 10. Sincronización Condicional 
Implementar un programa concurrente en Java con SimpleConcurrent que tenga los siguientes requisitos: 
● El programa consta de 3 procesos. 
● Cada proceso escribe por pantalla varias letras y termina. El proceso 1 debe escribir la letra ‘A’ y la 
letra ‘B’. El proceso 2 debe escribir la letra ‘C’, la letra ‘D’ y la letra ‘E’. El proceso 3 debe escribir la 
letra ‘F’ y la letra ‘G’. 
● Los procesos deben sincronizarse para que se cumplan las siguientes relaciones de precedencia: 
 
 
 
● La sincronización condicional se deberá implementar con espera activa. 
 
 
 
 
 
A B 
D E 
G 
C 
F 
Proc1 
Proc2
2 
Proc3
2 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
6 
 
● Algunas de las posibles salidas de este programa son: 
● CFADBGE 
● CFDABGE 
● CFDAGBE 
● FCDAGBE 
● Algunas de las posibles salidas inválidas de este programa son: 
● ACFDBGE 
● CDFABGE 
 
Ejercicio 11. Cliente-Servidor con Proxy (1 petición) 
Implementar un programa concurrente formado por un proceso Servidor, un proceso Proxy y un proceso 
Cliente. 
 El proceso Cliente genera un número aleatorio, se lo envía al Proxy y espera su respuesta. Cuando 
la recibe, la imprime por pantalla y termina. 
 El proceso Proxy no hace nada hasta que recibe una petición. Cuando la recibe, el Proxy suma 1 al 
número enviado por el cliente y hace una petición al servidor con ese número, esperando su 
respuesta. Cuando el proceso Servidor reciba la petición, le suma 1 y se la envía de vuelta al proxy. 
Cuando el proxy recibe la petición del servidor, se la reenvía al cliente. 
 La solución deberá implementarse con espera activa. 
 
Ejercicio 12. Cliente-Servidor con Proxy (N peticiones) 
 
Implementar un programa concurrente similar al anterior con la diferencia de que el proceso Cliente hace 
10 peticiones, el proceso Proxy atendiendo esas 10 peticiones y se las redirige al proceso Servidor. El 
proceso servidor también responde a 10 peticiones. La solución deberá implementarse con espera activa. 
__MACOSX/Academia/Tema2/._Tema 2.1. Ejercicios.pdf
Academia/Tema2/ejemplos tema2.zip
ejemplo/Ejemplo1.java
ejemplo/Ejemplo1.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class Ejemplo1 {
    public static void repeat(String
text) {
        for (int i = 0; i < 5; i++) {
            println(text);
        }
    }
    public static void printText() {
        println("B1");
        println("B2");
        println("B3");
    }
    public static void main(String[] args) {
        createThread("repeat", "XXXXX");
        createThread("repeat", "-----");
        createThread("printText");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutua.java
ejemplo/ExcMutua.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ExcMutua {
    public static void p1() {
        
        while(true) {
            // Sección bajo EM
            printlnI("P1_EM1 ");
            printlnI("P1_EM2 ");
            // Sección sin EM
            printlnI("P1_1 ");
            printlnI("P1_2 ");
        }
    }
    public static void main(String[] args) {
        createThreads(2, "p1");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaEA.java
ejemplo/ExcMutuaEA.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ExcMutuaEA {
    public static void p1() {
        for (int i = 0; i < 5; i++) {
            enterMutex();
            // Sección bajo EM
            printlnI("P1_EM1 ");
            printlnI("P1_EM2 ");
            exitMutex();
            // Sección sin EM
            printlnI("P1_1 ");
            printlnI("P1_2 ");
        }
    }
    public static void p2() {
        for (int i = 0; i < 5; i++) {
            enterMutex();
            // Sección bajo EM
            printlnI("P2_EM1 ");
            printlnI("P2_EM2 ");
            exitMutex();
            // Sección sin EM
            printlnI("P2_1 ");
            printlnI("P2_2 ");
        }
    }
    public static void main(String[] args) {
        createThread("p1");
        createThread("p2");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaEA2.java
ejemplo/ExcMutuaEA2.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ExcMutuaEA2 {
    public static void p() {
        for (int i = 0; i < 5; i++) {
            enterMutex();
            printlnI("********");
            printlnI("********");
            printlnI("********");
            exitMutex();
            printlnI("--------");
            printlnI("--------");
            printlnI("--------");
            printlnI("--------");
            printlnI("--------");
        }
    }
    public static void main(String[] args) {
        createThreads(4, "p");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaGenSem.java
ejemplo/ExcMutuaGenSem.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
import es.urjc.etsii.code.concurrency.SimpleSemaphore;
public class ExcMutuaGenSem {
    private static SimpleSemaphore em;
    
    public static void p() {
        
        for (int i = 0; i < 5; i++) {
            em.acquire();
            printlnI("P_EM1");
            printlnI("P_EM2");
            em.release();
            // Sección No Crítica
            printlnI("P_1");
            printlnI("P_2");
        }
    }
    public static void main(String[] args) {
        em = new SimpleSemaphore(3);
        createThreads(5,"p");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaIntento1.java
ejemplo/ExcMutuaIntento1.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ExcMutuaIntento1 {
    private static volatile boolean ocupado;
    public static void p1() {
        
        for (int i = 0; i < 5; i++) {
            
            while (ocupado);
            ocupado = true;
            // Sección bajo EM
            printlnI("P1_EM1 ");
            printlnI("P1_EM2 ");
            ocupado = false;
            // Sección sin EM
            printlnI("P1_1 ");
            printlnI("P1_2 ");
        }
    }
    public static void p2() {
        for (int i = 0; i < 5; i++) {
            
            while (ocupado);
            ocupado = true;
            // Sección bajo EM
            printlnI("P1_EM1 ");
            printlnI("P1_EM2 ");
            ocupado = false;
            // Sección sin EM
            printlnI("P1_1 ");
            printlnI("P1_2 ");
        }
    }
    public static void main(String[] args) {
        ocupado = false;
        createThread("p1");
        createThread("p2");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaIntento2.java
ejemplo/ExcMutuaIntento2.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ExcMutuaIntento2 {
    private static volatile boolean p1p;
    private static volatile boolean p2p;
    public static void p1() {
        
        for (int i = 0; i < 5; i++) {
            
            p1p = true;
            while (p2p);
            // Sección bajo EM
            printlnI("P1_EM1");
            printlnI("P1_EM2");
            p1p = false;
            // Sección sin EM
            printlnI("P1_1");
            printlnI("P1_2");
        }
    }
    public static void p2() {
        for (int i = 0; i < 5; i++) {
            
            p2p = true;
            while (p1p);
            // Sección bajo EM
            printlnI("P2_EM1");
            printlnI("P2_EM2");
            
            p2p = false;
            // Sección sin EM
            printlnI("P2_1");
            printlnI("P2_2");
        }
    }
    public static void main(String[] args) {
        p1p = false;
        p2p = false;
        createThread("p1");
        createThread("p2");
        startThreadsAndWait();
    }
}
ejemplo/ExcMutuaSem.java
ejemplo/ExcMutuaSem.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
import es.urjc.etsii.code.concurrency.SimpleSemaphore;
public class ExcMutuaSem {
    private static SimpleSemaphore em;
    
    public static void p() {
        
        for (int i = 0; i < 5; i++) {
            //Sección bajo EM
            em.acquire();
            printlnI("P_EM1");
            printlnI("P_EM2");
            em.release();
            // Sección sin EM
            printlnI("P_1");
            printlnI("P_2");
        }
    }
    public static void main(String[] args) {
        em = new SimpleSemaphore(1);
        createThreads(2,"p");
        startThreadsAndWait();
    }
}
ejemplo/IncDec.java
ejemplo/IncDec.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class IncDec {
    static volatile double x;
    public static void inc() {
        x = x + 1;
    }
    public static void dec() {
        x = x - 1;
    }
    public static void main(String[] args) {
        x = 0;
        createThread("inc");
        createThread("dec");
        startThreadsAndWait();
        println("x:" + x);
    }
}
ejemplo/IncDecEM.java
ejemplo/IncDecEM.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class IncDecEM {
    static volatile double x;
    public static void inc() {
        for (int i = 0; i < 10000000; i++) {
            enterMutex();
            x = x + 1;
            exitMutex();
        }
    }
    public static void
dec() {
        for (int i = 0; i < 10000000; i++) {
            enterMutex();
            x = x - 1;
            exitMutex();
        }
    }
    public static void main(String[] args) {
        x = 0;
        createThread("inc");
        createThread("dec");
        startThreadsAndWait();
        println("x:" + x);
    }
}
ejemplo/IncDecEM2.java
ejemplo/IncDecEM2.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class IncDecEM2 {
    static volatile double x;
    static volatile double y;
    public static void inc() {
        enterMutex("x");
        x = x + 1;
        exitMutex("x");
    }
    public static void dec() {
        enterMutex("x");
        x = x - 1;
        exitMutex("x");
    }
    public static void incY() {
        enterMutex("y");
        y = y + 1;
        exitMutex("y");
    }
    public static void decY() {
        enterMutex("y");
        y = y - 1;
        exitMutex("y");
    }
    public static void main(String[] args) {
        x = 0; y=0;     
        createThread("inc");
        createThread("dec");
        
        createThread("incY");
        createThread("decY");
        
        startThreadsAndWait();
        println("x:" + x);
        println("y:" + y);
    }
}
ejemplo/IncDecFor.java
ejemplo/IncDecFor.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class IncDecFor {
    static volatile double x;
    public static void inc() {
        for (int i = 0; i < 10000000; i++) {
            x = x + 1;
        }
    }
    public static void dec() {
        for (int i = 0; i < 10000000; i++) {
            x = x - 1;
        }
    }
    public static void main(String[] args) {
        x = 0;
        createThread("inc");
        createThread("dec");
        startThreadsAndWait();
        println("x:" + x);
    }
}
ejemplo/MaxMin.java
ejemplo/MaxMin.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class MaxMin {
    static volatile double n1, n2, min, max;
    public static void min() {
        min = n1 < n2 ? n1 : n2;
    }
    public static void max() {
        max = n1 > n2 ? n1 : n2;
    }
    public static void main(String[] args) {
        n1 = 3;
        n2 = 5; // I0
        min();  // I1
        max();  // I2
        println("m:" + min + " M:" + max); // I3
        
    }
}
ejemplo/MaxMinCon.java
ejemplo/MaxMinCon.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class MaxMinCon {
    static volatile double n1, n2, min, max;
    public static void min() {
        min = n1 < n2 ? n1 : n2;
    }
    public static void max() {
        max = n1 > n2 ? n1 : n2;
    }
    public static void main(String[] args) {
        n1 = 3;
        n2 = 5; // I0
        createThread("min"); // I1
        createThread("max"); // I2
        startThreadsAndWait();
        println("m:" + min + " M:" + max); // I3
    }
}
ejemplo/ProdConsBuffer.java
ejemplo/ProdConsBuffer.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
import es.urjc.etsii.code.concurrency.SimpleSemaphore;
public class ProdConsBuffer {
    private static final int BUFFER_SIZE = 10;
    
    private static int[] datos = new int[BUFFER_SIZE];
    private static int posInser = 0;
    private static int posSacar = 0;
    private static SimpleSemaphore nHuecos = new SimpleSemaphore(BUFFER_SIZE);
    private static SimpleSemaphore nProductos = new SimpleSemaphore(0);
    private static SimpleSemaphore emPosInser = new SimpleSemaphore(1);
    private static SimpleSemaphore emPosSacar = new SimpleSemaphore(1);
    
    public static void insertarBuffer(int dato) {       
        nHuecos.acquire();      
        
        emPosInser.acquire();
        datos[posInser] = dato;
        posInser = (posInser+1) % datos.length;
        emPosInser.release();
        
        nProductos.release();
    }
    public static int sacarBuffer() {       
        nProductos.acquire();
        
        emPosSacar.acquire();
        int dato = datos[posSacar];
        posSacar = (posSacar+1) % datos.length;
        emPosSacar.release();
        
        nHuecos.release();  
        
        return dato;
    }
    public static void productor() {
        for (int i = 0; i < 20; i++) {
            sleepRandom(500);
            insertarBuffer(i);
        }
    }
    public static void consumidor() {
        while (true) {
            int data = sacarBuffer();
            sleepRandom(500);
            print(data + " ");
        }
    }
    public static void main(String[] args) {        
        createThreads(5, "productor");
        createThreads(3, "consumidor");
        startThreadsAndWait();
    }
}
ejemplo/ProdConsBufferMal.java
ejemplo/ProdConsBufferMal.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class ProdConsBufferMal {
    private static int[] datos = new int[10];
    private static int posInser = 0;
    private static int posSacar = 0;
    public static void insertarBuffer(int dato) {
        datos[posInser] = dato;
        posInser = (posInser + 1) % datos.length;
    }
    public static int sacarBuffer() {
        int dato = datos[posSacar];
        posSacar = (posSacar + 1) % datos.length;
        return dato;
    }
    public static void productor() {
        for (int i = 0; i < 20; i++) {
            sleepRandom(500);
            insertarBuffer(i);
        }
    }
    public static void consumidor() {
        while (true) {
            int data = sacarBuffer();
            sleepRandom(500);
            print(data + " ");
        }
    }
    public static void main(String[] args) {        
        createThreads(5, "productor");
        createThreads(3, "consumidor");
        startThreadsAndWait();
    }
}
ejemplo/SincCond.java
ejemplo/SincCond.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
public class SincCond {
    static volatile boolean continuar;
    public static void a() {
        print("PA1 ");
        continuar = true;
        print("PA2 ");
    }
    public static void b() {
        print("PB1 ");
        while (!continuar);
        print("PB2 ");
    }
    public static void main(String[] args) {
        continuar = false;
        createThread("a");
        createThread("b");
        startThreadsAndWait();
    }
}
ejemplo/SincCondSem.java
ejemplo/SincCondSem.java
package ejemplo;
import static es.urjc.etsii.code.concurrency.SimpleConcurrent.*;
import es.urjc.etsii.code.concurrency.SimpleSemaphore;
public class SincCondSem {
    static SimpleSemaphore continuar;
    public static void a() {
        print("PA1 ");
        continuar.release();
        print("PA2 ");
    }
    public static void b() {
        print("PB1 ");
        continuar.acquire();
        print("PB2 ");
    }
    public static void main(String[] args) {
        continuar = new SimpleSemaphore(0);
        createThread("a");
        createThread("b");
        startThreadsAndWait();
    }
}
__MACOSX/Academia/Tema2/._ejemplos tema2.zip
Academia/Tema2/Tema2.1 - Ejercicios (soluciones).pdf
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida
1 
 
 
 
Ejercicios Tema 2. Parte 1 
Concurrencia con Memoria Compartida 
Soluciones 
Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la 
sincronización con memoria compartida. 
Ejercicio 1. Productor Consumidor 
Se desea implementar un programa concurrente con un proceso que produce información (productor) y 
otro proceso que hace uso de esa información (consumidor). El proceso productor genera un número 
aleatorio y termina. El proceso consumidor muestra por pantalla el número generado y termina. 
Solución: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer1_ProdCons { 
 
 static volatile boolean producido; 
 static volatile double producto; 
 
 public static void productor() { 
 
 producto = Math.random(); 
 producido = true; 
 } 
 
 public static void consumidor() { 
 
 while (!producido); 
 print("Producto: "+producto); 
 } 
 
 public static void main(String[] args) { 
 
 producido = false; 
 
 createThread("productor"); 
 createThread("consumidor"); 
 
 startThreadsAndWait(); 
 } 
} 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
2 
 
Ejercicio 2. Productor Consumidor Infinito 
Se desea ampliar el programa del Ejercicio 1 de forma que el proceso productor esté constantemente 
produciendo números consecutivos. El proceso consumidor estará constantemente consumiendo los 
productos. No se puede quedar ningún producto sin consumir. No se puede consumir dos veces el mismo 
producto. 
Intento erróneo de solución: 
En un primer intento, puede considerarse una solución errónea como la que se muestra a continuación. 
Esta solución no es correcta porque existen intercalaciones problemáticas que no son evidentes. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer2_ProdConsN_Mal { 
 
 static volatile boolean producido; 
 static volatile boolean consumido; 
 static volatile double producto; 
 
 public static void productor() { 
 double num = 0; 
 
 for(int i=0; i<5; i++){ 
 producto = num++; 
 producido = true; 
 sleep(50); //Simula livelock 
 consumido = false; 
 while(!consumido); 
 } 
 } 
 
 public static void consumidor() { 
 
 for(int i=0; i<5; i++){ 
 while (!producido); 
 println("Producto: " + producto); 
 producido = false; 
 consumido = true; 
 } 
 } 
 
 public static void main(String[] args) { 
 
 producido = false; 
 consumido = false; 
 
 createThread("productor"); 
 createThread("consumidor"); 
 
 startThreadsAndWait(); 
 } 
} 
El programa tiene un problema de interbloqueo activo. Si se descomenta la línea de sleep(50) se puede 
emular que el productor se ejecuta en un procesador lento y se puede forzar el problema. Una posible 
intercalación de sentencias es: 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
3 
 
 
 Productor consumidor producto producido consumido 
1 while(¡producido); false true 
2 producto = num++; 0 false true 
3 producido = true; 0 true true 
4 while(¡producido); 0 true true 
5 
 println("Producto:" 
+producto); 
0 
 
6 consumido = true; 0 true true 
7 producido = false; 0 false true 
8 consumido = false; 0 false false 
9 while(¡producido); 0 false false 
10 while(¡consumido); 0 false false 
Se produce un interbloqueo. A simple vista es complicado ver el problema porque se solapan dos 
iteraciones del bucle infinito, pero si se desenreda el bucle (copiando el cuerpo del bucle varias veces) se 
puede apreciar mejor el problema. La solución consiste en actualizar la variable que controla la 
sincronización con el otro proceso antes de darle paso. En la siguiente implementación el proceso 
productor pone la variable consumido a false antes de poner producido a true. El proceso consumidor 
pone la variable producido a false antes de poner consumido a true. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer2_ProdConsN_1 { 
 
 static volatile boolean producido; 
 static volatile boolean consumido; 
 static volatile double producto; 
 
 public static void productor() { 
 double num = 0; 
 for(int i=0; i<5; i++){ 
 producto = num++; 
 consumido = false; 
 sleep(50); 
 producido = true; 
 while(!consumido); 
 } 
 } 
 
 public static void consumidor() { 
 
 for(int i=0; i<5; i++){ 
 while (!producido); 
 println("Producto: " + producto); 
 producido = false; 
 consumido = true; 
 } 
 } 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
4 
 
 public static void main(String[] args) { 
 
 producido = false; 
 consumido = false; 
 
 createThread("productor"); 
 createThread("consumidor"); 
 
 startThreadsAndWait(); 
 } 
} 
Este ejemplo también se puede implementar con una única variable. La variable produciendo, que 
básicamente lo que hace es dar turnos de uno a otro. Cada proceso tiene un turno, y cuando termina su 
turno, le cede el turno al otro proceso. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer2_ProdConsN_2 { 
 
 static volatile boolean produciendo; 
 static volatile double producto; 
 
 public static void productor() { 
 double num = 0; 
 for(int i=0; i<5; i++){ 
 producto = num++; 
 sleep(500); //Simula tiempo de producción 
 produciendo = false; 
 while(!produciendo); 
 } 
 } 
 
 public static void consumidor() { 
 
 for(int i=0; i<5; i++){ 
 while (produciendo); 
 println("Producto: " + producto); 
 sleep(500); //Simula tiempo de consumo 
 produciendo = true; 
 } 
 } 
 
 public static void main(String[] args) { 
 
 produciendo = true; 
 
 createThread("productor"); 
 createThread("consumidor"); 
 
 startThreadsAndWait(); 
 } 
} 
El problema de las soluciones anteriores es que no aprovechan bien los recursos disponibles. Si la máquina 
en la que se ejecuta el programa dispone de dos o más procesadores, pese a que cada hilo estuviera 
ejecutando en su propio procesador, con esta sincronización no podrían realizar trabajo en paralelo. Tal y 
como está diseñado, cuando un proceso está produciendo, el otro no puede consumir. Es decir, el tiempo 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
5 
 
total de ejecución de este programa es de aproximadamente 5 segundos. Por cada elemento, 0,5s para 
producir y 0,5 segundo para consumir. 
A continuación se propone una versión más eficiente, porque permite que un hilo produzca a la misma vez 
que otro hilo está consumiendo. Para ello, basta con crear una variable local almacén, que guarde el 
producto en el proceso productor hasta que el consumidor está listo para consumir ese valor. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer2_ProdConsN_3 { 
 
 static volatile boolean produciendo; 
 static volatile double producto; 
 
 public static void productor() { 
 double num = 0; 
 for(int i=0; i<5; i++){ 
 double almacen = num++; // Crea el producto 
 sleep(500); //Simula tiempo de consumo 
 while (!produciendo); // Espera para colocarlo 
 producto = almacen; // Guardar el valor 
 produciendo = false; // Se notifica que ya hay valor 
 } 
 } 
 
 public static void consumidor() { 
 
 for(int i=0; i<5; i++){ 
 while (produciendo); 
 println("Producto: " +
producto); 
 sleep(500); //Simula tiempo de consumo 
 produciendo = true; 
 } 
 } 
 
 public static void main(String[] args) { 
 
 produciendo = true; 
 
 createThread("productor"); 
 createThread("consumidor"); 
 
 startThreadsAndWait(); 
 } 
} 
El tiempo de ejecución de esta versión es algo más de 3s, porque la producción y el consumo se realizan en 
paralelo (salvo en el primer elemento, lo que añade 0,5 segundos al total). 
Ejercicio 3. Cliente Servidor 1 Petición 
Programa formado por un proceso servidor y otro proceso cliente. El proceso cliente hace una petición al 
proceso servidor (en forma de número aleatorio) y espera su respuesta, cuando la recibe, la procesa. El 
proceso servidor no hace nada hasta que recibe una petición, momento en el que suma 1 al número 
enviado en la petición y contesta con ese valor. El proceso cliente procesa la respuesta mostrándola por 
pantalla. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
6 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer3_ClienteServidor { 
 
 static volatile boolean pedido; 
 static volatile boolean respondido; 
 
 static volatile double peticion; 
 static volatile double respuesta; 
 
 public static void server() { 
 
 while(!pedido); 
 respuesta = peticion + 1; 
 respondido = true; 
 } 
 
 public static void client() { 
 
 peticion = Math.random(); 
 pedido = true; 
 while (!respondido); 
 print("Response: "+respuesta); 
 } 
 
 public static void main(String[] args) { 
 
 pedido = false; 
 respondido = false; 
 
 createThread("client"); 
 createThread("server"); 
 
 startThreadsAndWait(); 
 } 
} 
 
Ejercicio 4. Cliente Servidor N Peticiones 
Se desea ampliar el programa anterior de forma que el proceso cliente esté constantemente haciendo 
peticiones y el proceso servidor atendiendo a las mismas. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer4_ClienteServidorN { 
 
 static volatile boolean pedido; 
 static volatile boolean respondido; 
 
 static volatile double peticion; 
 static volatile double respuesta; 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
7 
 
 public static void server() { 
 
 while (true) { 
 while (!pedido); 
 pedido = false; 
 respuesta = peticion + 1; 
 respondido = true; 
 } 
 } 
 
 public static void client() { 
 
 while (true) { 
 peticion = Math.random(); 
 pedido = true; 
 while (!respondido); 
 respondido = false; 
 println("Response: " + respuesta); 
 } 
 } 
 
 public static void main(String[] args) { 
 
 pedido = false; 
 respondido = false; 
 
 createThread("client"); 
 createThread("server"); 
 
 startThreadsAndWait(); 
 } 
} 
 
Ejercicio 5. Museo 
Existen 3 personas en el mundo, 1 museo, y sólo cabe una persona dentro del museo. Las personas realizan 
cuatro acciones dentro del museo: 
 Cuando entran al museo saludan: “hola!” 
 Cuando ven el museo se sorprenden: “qué bonito!” y “alucinante!” 
 Cuando se van del museo se despiden: “adios” 
 Cuando salen del museo se van a dar un “paseo” 
Después del paseo, les ha gustado tanto que vuelven a entrar. Se pide: 
 Número de métodos diferentes que ejecutarán los procesos 
 Número de procesos del programa concurrente y de qué tipo son 
 Escribir lo que hace cada proceso 
 Identificar los recursos compartidos 
 Identificar la sección o secciones bajo exclusión mutua 
 Escribir el programa completo 
Solución: 
 Número de métodos diferentes que ejecutarán los procesos: un único método “persona” 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
8 
 
 Número de procesos del programa concurrente y qué método ejecutan: 3 procesos que ejecutan el 
método “persona” cada uno. 
 Escribir lo que hace cada proceso: 
 public static void persona() { 
 
 while (true) { 
 printlnI("hola!"); 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 printlnI("adiós"); 
 printlnI("paseo"); 
 } 
 } 
 Identificar los recursos compartidos: El museo 
 Identificar la sección o secciones bajo exclusión mutua: La sección bajo exclusión mutua es el 
conjunto de instrucciones que se ejecutan dentro del museo. 
 Escribir el programa completo: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer5_Museo { 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex(); 
 printlnI("hola!"); 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 printlnI("adiós"); 
 exitMutex(); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
En el ejemplo anterior se puede apreciar el uso del método createThreads(3,”persona”) que permite crear 
varios procesos ejecutando el mismo método. 
Ejercicio 6. Museo con infinitas personas 
Considerar que caben infinitas personas dentro del museo. Cada persona al entrar tiene que saludar 
diciendo cuántas personas hay en el museo: “hola, somos 3”. Al despedirse tiene que decir el número de 
personas que quedan tras irse: “adiós a los 2”. 
Solución: 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
9 
 
Del tema 1 de Introducción se sabe que incrementar o decrementar una variable no es una operación 
atómica de grano fino en Java, por lo tanto habrá que hacer que sea una instrucción atómica de grano 
grueso. Es decir, el contador debe estar bajo exclusión mutua, tanto en el incremento como en el 
decremento. En una primera aproximación, se podría pensar que esta puede ser la solución, aunque es 
errónea: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer6_Museo_Mal { 
 
 static volatile int personas; 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex(); 
 personas++; 
 exitMutex(); 
 
 printlnI("hola, somos "+personas); 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 
 enterMutex(); 
 personas--; 
 exitMutex(); 
 
 printlnI("adiós a los "+personas); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
Esta aproximación no es correcta porque se puede dar el caso de que una persona entre al museo e 
incremente la variable y no salude hasta que haya llegado más gente. En ese caso, varias personas podrían 
saludar al mismo número de personas en el museo. Dicho de otra manera… podría estar un rato en el 
museo sin saludar a nadie y eso es una falta de cortesía. La intercalación problemática: 
P1 P2 contador 
personas++; 1 
 personas++; 2 
printlnI("hola, somos 
"+personas); 
 2 
 printlnI("hola, somos 
"+personas); 
2 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
10 
 
La solución correcta es la siguiente: 
package exercises.mutex; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer6Museo2 { 
 
 static volatile int personas; 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex("contador"); 
 personas++; 
 printlnI("hola, somos "+personas); 
 exitMutex("contador"); 
 
 printlnI("qué bonito!");
printlnI("alucinante!"); 
 
 enterMutex("contador"); 
 personas--; 
 printlnI("adiós a los "+personas); 
 exitMutex("contador"); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
Ejercicio 7. Museo con regalo 
Para incentivar las visitas, cuando una persona entre en el museo estando vacío, será obsequiado con un 
regalo. Las personas, después de saludar, dicen si les han dado un regalo (“Tengo regalo”) o si no (“No 
tengo regalo”). Las personas deben permitir que otras personas saluden entre su saludo y el comentario 
sobre el regalo. 
Solución: 
Una posible aproximación errónea es la siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer7_Museo_Mal1 { 
 
 static volatile int personas; 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
11 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex(); 
 personas++; 
 printlnI("hola, somos "+personas); 
 if(personas == 1){ 
 printlnI("Tengo regalo"); 
 } else { 
 printlnI("No tengo regalo"); 
 } 
 exitMutex(); 
 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 
 enterMutex(); 
 personas--; 
 printlnI("adiós a los "+personas); 
 exitMutex(); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
El problema de esta solución es que no permite que nadie entre o salga del museo mientras la persona está 
diciendo si tiene o no regalo y tal y como dice el enunciado, eso se debería permitir. En cierta forma está 
limitando la concurrencia. 
Para aumentar la concurrencia, se podría considerar la siguiente aproximación errónea: 
 
 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
12 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer7_Museo_Mal2 { 
 
 static volatile int personas; 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex(); 
 personas++; 
 printlnI("hola, somos "+personas); 
 exitMutex(); 
 
 if(personas == 1){ 
 printlnI("Tengo regalo"); 
 } else { 
 printlnI("No tengo regalo"); 
 } 
 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 
 enterMutex(); 
 personas--; 
 printlnI("adiós a los "+personas); 
 exitMutex(); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
Pero esta solución tiene un problema importante. El contador de personas está bajo exclusión mutua, por 
lo tanto no se pueden producir condiciones de carrera, no se pueden producir errores en la cuenta. No 
obstante, si se pueden producir errores de funcionamiento porque es posible que alguien que tenga regalo 
(por haber llegado con el museo vacío) no se lo diga a los demás si entra una persona desde que realizó su 
saludo. Es decir, al sacar el “if” de la sección crítica, cualquier proceso podría cambiar el valor del contador 
de personas y por lo tanto que el funcionamiento del programa fuera erróneo. 
Una solución más concurrente que la primera aproximación y sin los problemas de la segunda 
aproximación se puede conseguir usando la variable local: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer7_Museo_1 { 
 
 static volatile int personas; 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
13 
 
 public static void persona() { 
 
 while (true) { 
 
 boolean regalo = false; 
 enterMutex(); 
 if(personas == 0){ 
 regalo = true; 
 } 
 personas++; 
 printlnI("hola, somos "+personas); 
 exitMutex(); 
 
 if(regalo){ 
 printlnI("Tengo regalo"); 
 } else { 
 printlnI("No tengo regalo"); 
 } 
 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 
 enterMutex(); 
 personas--; 
 printlnI("adiós a los "+personas); 
 exitMutex(); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
También se puede conseguir una solución equivalente sin necesidad de utilizar la variable local: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer7_Museo_2 { 
 
 static volatile int personas; 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
14 
 
 public static void persona() { 
 
 while (true) { 
 
 enterMutex(); 
 personas++; 
 printlnI("hola, somos "+personas); 
 if(personas == 1){ 
 exitMutex(); 
 printlnI("Tengo regalo"); 
 } else { 
 exitMutex(); 
 printlnI("No tengo regalo"); 
 } 
 
 printlnI("qué bonito!"); 
 printlnI("alucinante!"); 
 
 enterMutex(); 
 personas--; 
 printlnI("adiós a los "+personas); 
 exitMutex(); 
 
 printlnI("paseo"); 
 } 
 } 
 
 public static void main(String[] args) { 
 personas = 0; 
 createThreads(3, "persona"); 
 startThreadsAndWait(); 
 } 
} 
Como se puede ver en la solución, se puede dar el caso de que la entrada en una sección bajo exclusión 
mutua se ejecute en un sentencia y que su correspondiente salida se ejecute en la parte then o en la parte 
else de una setencia if. Este mecanismo es necesario si se quieren ejecutar acciones distintas al salir de la 
exclusión mutua sobre una variable que se está consultando en la condición del if. 
Ejercicio 8. Preguntas Cortas 
8.1) Dado el programa siguiente ¿podría darse el caso de que uno o ninguno de los dos procesos finalizase? 
Razona la respuesta. 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
15 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer8_1 { 
 
 static volatile boolean fin = false; 
 
 public static void proceso() { 
 int i = 0; // I1 
 while (!fin) { // I2 
 i++; // I3 
 if (i == 3) { // I4 
 fin = true; // I5 
 } else { 
 fin = false; // I6 
 } 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(2, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
Solución: 
En el código existe una variable global compartida (Fin) por todos los procesos y acceden a ella sin 
exclusión mutua, esto puede plantear bastantes problemas. 
Puede darse el caso que un proceso ponga la variable fin a true porque ha llegado a la tercera iteración, 
y antes de que consulte la condición del While el otro proceso la ponga fin a false, si esto se repite para 
el otro proceso ninguno de los dos terminaría. 
Una secuencia de instrucciones problemática sería: 
Proceso_1 ejecuta el bucle 3 veces hasta I5 inclusive y pone Fin a true. 
Proceso_2 ejecuta el bucle 1 vez hasta I6 inclusive y pone Fin a false. 
Proceso_1 ejecuta el bucle 1 vez hasta I4 inclusive. 
Proceso_2 ejecuta el bucle 2 veces
hasta I5 inclusive y pone Fin a true. 
Proceso_1 ejecuta I6 y pone Fin a false. 
A partir de ese momento ningún proceso abandonará el bucle infinito. 
8.2) Utilizando la espera activa se pretende resolver el problema de sincronización condicional de dos 
procesos concurrentes cuyo ciclo de vida es el siguiente: 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
16 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer8_2 { 
 
 static volatile boolean flag = false; 
 
 public static void p1(){ 
 
 while(true){ 
 print("A"); 
 flag = true; 
 print("B"); 
 } 
 } 
 
 public static void p2(){ 
 while(true){ 
 print("C"); 
 while(!flag); 
 print("D"); 
 } 
 } 
 
 public static void main(String[] args) { 
 createThread("p1"); 
 createThread("p2"); 
 startThreadsAndWait(); 
 } 
} 
a) Después de la primera sincronización ¿se sincronizarán correctamente ambos procesos? Razone la 
respuesta. 
No, ya que después de la primera iteración el flag queda a “true” y el proceso P2 puede ejecutar D sin 
esperar a que P1 ejecute antes A. 
b) Qué tipo de interacción se produce entre los procesos P1 y P2? ¿En qué estados pueden estar los 
procesos P1 y P2? 
La interacción es una Sincronización Condicional. Los procesos pueden estar en los estados Preparado y 
Ejecución. No pueden estar en estado bloqueado porque no se usa ninguna herramienta de sincronización. 
8.3) ¿Por qué razón el acceso (lecturas y/o escrituras) a variables globales compartidas por varios procesos 
debe realizarse bajo exclusión mutua en el modelo de variables compartidas? 
Porque habitualmente la manipulación que se hace de las variables compartidas no son en general 
instrucciones atómicas. Por ejemplo, el incremento de una variable de tipo entero no es una operación 
atómica. En case de que varios procesos accedieran a una variable compartida sin que esté bajo exclusión 
mutua podrían producirse intercalaciones de las instrucciones atómicas en las que se descomponen que 
provocarían la inconsistencia de tales variables. 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
17 
 
8.4) ¿En qué consiste el problema de la exclusión mutua? ¿Cuáles son los requisitos de su solución? 
El problema de la exclusión mutua consiste en un conjunto de procesos concurrentes que deben ejecutar 
repetidamente un cierto código llamado sección bajo exclusión mutua, de manera que sólo un proceso 
puede estar ejecutando ese código en cada instante. 
8.5) Dibuje el diagrama de estados de un proceso 
 
8.6) ¿Qué diferencia hay entre los estados de preparado y de ejecución de un proceso? 
En el estado de preparado, el proceso está dispuesto para ejecutar instrucciones, a falta únicamente de un 
procesador. En el estado de ejecución, el proceso está ejecutando instrucciones en un procesador. 
8.7) Defina en qué consisten las propiedades de seguridad de un programa concurrente. Cite dos 
propiedades de este tipo. 
Aquéllas que deben ser ciertas en todo instante de la ejecución del programa. Por tanto, aseguran que 
nada malo ocurrirá nunca durante una ejecución. 
Dos propiedades de seguridad son Exclusión Mutua y Ausencia de Interbloqueo Pasivo. 
8.8) ¿Qué es un interbloqueo de procesos? 
Es un estado de un programa concurrente en el que un conjunto de procesos permanece esperando un 
suceso que sólo puede provocar un proceso del propio conjunto, con lo que todos los procesos esperan 
indefinidamente. El interbloqueo puede ser pasivo (deadlock) o activo (livelock). 
8.9) ¿Cuáles son las dos actividades principales necesarias para gestionar la ejecución de procesos 
concurrentes en multiprogramación? 
Planificación y Despacho 
8.10) En la gestión de procesos concurrentes, explique la diferencia entre planificación y despacho. 
La planificación consiste en decidir qué proceso va a ejecutarse cuando quede libre el procesador. 
El despacho consiste en otorgar el control de la CPU al proceso elegido por el planificador. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
18 
 
Ejercicio 9. Downloader 
a) Se quiere implementar una aplicación para descargar ficheros. La aplicación debe tener la capacidad de 
descargar un único fichero, pero debe ser capaz de descargar varios fragmentos del fichero de manera 
concurrente para aprovechar de forma más eficiente la red. 
Para simplificar la aplicación, consideramos que un fichero se representa en memoria como un array de 
enteros. Internamente, la aplicación dispone de una serie de procesos que van descargando los diferentes 
fragmentos del fichero (posiciones del array). Los procesos están ejecutando tres acciones: primero se 
determina el siguiente fragmento a descargar, a continuación se descarga ese fragmento, y por último se 
guarda el fragmento descargado en el array que representa el fichero. 
La solución se puede implementar con espera activa o con semáforos. 
La descarga de los distintos fragmentos se simulará con el método: 
private static int descargaDatos(int numFragmento) { 
 sleepRandom(1000); 
 return numFragmento * 2; 
} 
Cuando se ha terminado de descargar completamente el fichero, se muestra por pantalla y el programa 
termina. Para mostrar el fichero por pantalla se utilizará el siguiente método: 
private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
} 
A continuación se presenta un esqueleto de la aplicación de descargas: 
package ejercicio.downloader; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer_9A_Downloader_Plantilla { 
 
 private static final int N_FRAGMENTOS = 10; 
 private static final int N_HILOS = 3; 
 
 private static volatile int[] fichero = new int[N_FRAGMENTOS]; 
 
 //Add the attributes you need 
 
 private static int descargaDatos(int numFragmento) { 
 sleepRandom(1000); 
 return numFragmento * 2; 
 } 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
19 
 
 private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
 } 
 
 public static void downloader() { 
 
 // Mientras hay fragmentos que descargar... 
 //Descargar los datos del siguiente fragmento 
 //Almacenar los datos en el array 
 } 
 
 public static void main(String[] args) { 
 
 createThreads(N_HILOS, "downloader"); 
 
 startThreadsAndWait(); 
 
 mostrarFichero(); 
 } 
 
} 
Solución usando espera activa: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer9A_Downloader { 
 
 private static final int N_FRAGMENTOS = 10; 
 private static final int N_HILOS = 3; 
 
 private static volatile int[] fichero = new int[N_FRAGMENTOS]; 
 private static volatile int fragPendiente = 0; 
 
 private static int descargarDatos(int numFragment) { 
 sleepRandom(1000); 
 return numFragment * 2; 
 } 
 
 private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
 } 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería
de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
20 
 
 public static void downloader() { 
 
 while (true) { 
 
 enterMutex(); 
 if (fragPendiente == N_FRAGMENTOS) { 
 exitMutex(); 
 break; 
 } 
 
 int fragDescargar = fragPendiente; 
 fragPendiente++; 
 exitMutex(); 
 
 println(getThreadName() + ": Descargando fragmento " + fragDescargar); 
 
 int downloadedData = descargarDatos(fragDescargar); 
 
 println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); 
 
 fichero[fragDescargar] = downloadedData; 
 
 } 
 } 
 
 public static void main(String[] args) { 
 
 createThreads(N_HILOS, "downloader"); 
 
 startThreadsAndWait(); 
 
 mostrarFichero(); 
 } 
 
} 
b) En la plantilla anterior, la impresión del fichero se realiza en el hilo principal. Se pide implementar el 
mismo programa de descarga de ficheros pero esta vez la impresión será realizada por el último proceso 
que guarde un fragmento descargado en el fichero. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer9B_Downloader { 
 
 private static final int N_FRAGMENTOS = 10; 
 private static final int N_HILOS = 3; 
 
 private static volatile int[] fichero = new int[N_FRAGMENTOS]; 
 private static volatile int fragPendiente = 0; 
 private static volatile int hilosTerminados = 0; 
 
 private static int descargarDatos(int numFragment) { 
 sleepRandom(1000); 
 return numFragment * 2; 
 } 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
21 
 
 private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
 } 
 
 public static void downloader() { 
 
 descargaFragmentos(); 
 
 boolean mostrar = false; 
 
 enterMutex("hilosTerminados"); 
 hilosTerminados++; 
 mostrar = hilosTerminados == N_HILOS; 
 exitMutex("hilosTerminados"); 
 
 if(mostrar){ 
 mostrarFichero(); 
 } 
 } 
 
 private static void descargaFragmentos() { 
 while (true) { 
 
 enterMutex("fragPendiente"); 
 if (fragPendiente == N_FRAGMENTOS) { 
 exitMutex("fragPendiente"); 
 break; 
 } 
 
 int fragDescargar = fragPendiente; 
 fragPendiente++; 
 exitMutex("fragPendiente"); 
 
 println(getThreadName() + ": Descargando fragmento " + fragDescargar); 
 
 int downloadedData = descargarDatos(fragDescargar); 
 
 println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); 
 
 fichero[fragDescargar] = downloadedData; 
 } 
 } 
 
 public static void main(String[] args) { 
 
 createThreads(N_HILOS, "downloader"); 
 
 startThreadsAndWait(); 
 } 
} 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
22 
 
 
Ejercicio 10. Sincronización Condicional 
Implementar un programa concurrente en Java con SimpleConcurrent que tenga los siguientes requisitos: 
● El programa consta de 3 procesos. 
● Cada proceso escribe por pantalla varias letras y termina. El proceso 1 debe escribir la letra ‘A’ y la 
letra ‘B’. El proceso 2 debe escribir la letra ‘C’, la letra ‘D’ y la letra ‘E’. El proceso 3 debe escribir la 
letra ‘F’ y la letra ‘G’. 
● Los procesos deben sincronizarse para que se cumplan las siguientes relaciones de precedencia: 
 
 
 
● La sincronización condicional se deberá implementar con espera activa. 
● Algunas de las posibles salidas de este programa son: 
● CFADBGE 
● CFDABGE 
● CFDAGBE 
● FCDAGBE 
● Algunas de las posibles salidas inválidas de este programa son: 
● ACFDBGE 
● CDFABGE 
Solución: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer10_SincCond { 
 
 static volatile boolean continuarA; 
 static volatile boolean continuarD; 
 static volatile boolean continuarE1; 
 static volatile boolean continuarE2; 
 
 public static void proc1() { 
 while(!continuarA); 
 print("A"); 
 print("B"); 
 continuarE1 = true; 
 } 
 
 
 
 
 
 
A B 
D E 
G 
C 
F 
Proc1 
Proc2
2 
Proc3
2 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
23 
 
 public static void proc2() { 
 print("C"); 
 continuarA = true; 
 while(!continuarD); 
 print("D"); 
 while(!continuarE1); 
 while(!continuarE2); 
 print("E"); 
 } 
 
 public static void proc3() { 
 print("F"); 
 continuarD = true; 
 print("G"); 
 continuarE2 = true; 
 } 
 
 public static void main(String[] args) { 
 
 continuarA = false; 
 continuarD = false; 
 continuarE1 = false; 
 continuarE2 = false; 
 
 createThread("proc1"); 
 createThread("proc2"); 
 createThread("proc3"); 
 
 startThreadsAndWait(); 
 } 
} 
 
Ejercicio 11. Cliente-Servidor con Proxy (1 petición) 
Implementar un programa concurrente formado por un proceso Servidor, un proceso Proxy y un proceso 
Cliente. 
 El proceso Cliente genera un número aleatorio, se lo envía al Proxy y espera su respuesta. Cuando 
la recibe, la imprime por pantalla y termina. 
 El proceso Proxy no hace nada hasta que recibe una petición. Cuando la recibe, el Proxy suma 1 al 
número enviado por el cliente y hace una petición al servidor con ese número, esperando su 
respuesta. Cuando el proceso Servidor reciba la petición, le suma 1 y se la envía de vuelta al proxy. 
Cuando el proxy recibe la petición del servidor, se la reenvía al cliente. 
 La solución deberá implementarse con espera activa. 
 
Solución: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer11_ClienteServidorProxy { 
 
 static volatile boolean pedido; 
 static volatile boolean respondido; 
 
 static volatile double peticion; 
 static volatile double respuesta; 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
24 
 
 static volatile boolean pedidoProxy; 
 static volatile boolean respondidoProxy; 
 
 static volatile double peticionProxy; 
 static volatile double respuestaProxy; 
 
 public static void client() { 
 
 peticionProxy = Math.random(); 
 pedidoProxy = true; 
 while (!respondidoProxy); 
 print("Response: "+respuestaProxy); 
 } 
 
 public static void proxy() { 
 
 while (!pedidoProxy); 
 
 peticion = peticionProxy + 1; 
 pedido = true; 
 
 while (!respondido); 
 
 respuestaProxy = respuesta; 
 respondidoProxy = true; 
 } 
 
 public static void server() { 
 
 while(!pedido); 
 respuesta = peticion + 1; 
 respondido = true; 
 } 
 
 public static void main(String[] args) { 
 
 pedido = false; 
 respondido = false; 
 
 pedidoProxy = false; 
 respondidoProxy = false; 
 
 createThread("client"); 
 createThread("proxy"); 
 createThread("server"); 
 
 startThreadsAndWait(); 
 } 
} 
 
 
Ejercicio 12. Cliente-Servidor con Proxy (N peticiones) 
 
Implementar un programa concurrente similar al anterior con la diferencia de que el proceso Cliente hace 
10 peticiones, el proceso Proxy atendiendo esas 10 peticiones y se las redirige al proceso Servidor. El 
proceso servidor también responde a 10 peticiones. La solución deberá implementarse con espera activa. 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
25 
 
Solución: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*;
public class Ejer12_ClienteServidorProxyN { 
 
 static volatile boolean pedido; 
 static volatile boolean respondido; 
 
 static volatile double peticion; 
 static volatile double respuesta; 
 
 static volatile boolean pedidoProxy; 
 static volatile boolean respondidoProxy; 
 
 static volatile double peticionProxy; 
 static volatile double respuestaProxy; 
 
 public static void client() { 
 for (int i = 0; i < 10; i++) { 
 peticionProxy = Math.random(); 
 pedidoProxy = true; 
 while (!respondidoProxy); 
 respondidoProxy = false; 
 println("Response: " + respuestaProxy); 
 } 
 } 
 
 public static void proxy() { 
 
 for (int i = 0; i < 10; i++) { 
 while (!pedidoProxy); 
 pedidoProxy = false; 
 
 peticion = peticionProxy + 1; 
 pedido = true; 
 
 while (!respondido); 
 respondido = false; 
 
 respuestaProxy = respuesta; 
 respondidoProxy = true; 
 } 
 } 
 
 public static void server() { 
 for (int i = 0; i < 10; i++) { 
 while (!pedido); 
 pedido = false; 
 respuesta = peticion + 1; 
 respondido = true; 
 } 
 } 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
26 
 
 public static void main(String[] args) { 
 
 pedido = false; 
 respondido = false; 
 
 pedidoProxy = false; 
 respondidoProxy = false; 
 
 createThread("client"); 
 createThread("proxy"); 
 createThread("server"); 
 
 startThreadsAndWait(); 
 } 
} 
 
__MACOSX/Academia/Tema2/._Tema2.1 - Ejercicios (soluciones).pdf
Academia/Tema2/Tema 2.2. Ejercicios.pdf
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
1 
 
 
 
Ejercicios Tema 2. Parte 2 
Concurrencia con Memoria Compartida 
Soluciones 
Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la 
sincronización con memoria compartida. 
Ejercicio 13 
Una línea del metro de Madrid está formada por varios tramos de vía que son recorridos secuencialmente 
en un único sentido por diferentes trenes. 
El ciclo de vida de los procesos que controlan los trenes es el siguiente: 
 public static void tren(int numTren) { 
 
 sleepRandom(500); 
 recorrerTramoA(numTren); 
 
 sleepRandom(500); 
 recorrerTramoB(numTren); 
 
 sleepRandom(500); 
 recorrerTramoC(numTren); 
 } 
 
El esquema del programa completo es el siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer13A_Metro_Plantilla { 
 
 private static final int NUM_TRENES = 5; 
 
 public static void tren(int numTren) { 
 
 sleepRandom(500); 
 recorrerTramoA(numTren); 
 
 sleepRandom(500); 
 recorrerTramoB(numTren); 
 
 sleepRandom(500); 
 recorrerTramoC(numTren); 
 } 
 
 private static void recorrerTramoA(int numTren) { 
 printlnI("Entra TA T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TA T" + numTren); 
 } 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
2 
 
 
 private static void recorrerTramoB(int numTren) { 
 printlnI("Entra TB T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TB T" + numTren); 
 } 
 
 private static void recorrerTramoC(int numTren) { 
 printlnI("Entra TC T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TC T" + numTren); 
 } 
 
 public static void main(String args[]) { 
 for (int i = 0; i < NUM_TRENES; i++) { 
 createThread("tren", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
a) Se pide escribir todo el código de sincronización necesario para garantizar que se cumplan las siguientes 
restricciones: 
 Cada tramo sólo pueda estar ocupado por un tren en cada instante 
 No deben producirse interbloqueos 
 Si un tren puede recorrer un tramo que se encuentra libre, debe hacerlo sin retrasarse 
innecesariamente 
 Los trenes nunca pueden adelantarse unos a otros. 
 La implementación se realizará con semáforos. 
b) Ahora se pide generalizar la solución anterior para cualquier número de tramos. 
Ejercicio 14. Sincronización de barrera 
La Sincronización de Barrera es una sincronización condicional en la que los procesos tienen que esperar a 
que el resto de procesos lleguen al mismo punto para poder continuar su ejecución. Este tipo de 
sincronización es muy utilizada en programas concurrentes. 
Se pide implementar un programa concurrente con las siguientes características: 
 El programa tendrá N procesos del mismo tipo. 
 Cada proceso escribe la letra ‘A’, luego la ‘B’ y terminan 
 Los procesos tienen que esperar que todos hayan escrito la letra ‘A’ antes de poder escribir la ‘B’ 
 La implementación se realizará con semáforos. 
La idea básica al implementar la sincronización de barrera es usar un contador de procesos que han 
llegado a la barrera. Si el contador es menor que el número total de procesos, entonces el proceso se 
bloquea. Si el contador es igual al número total de procesos, entonces ese proceso desbloquea a los demás 
para que todos puedan proseguir su ejecución. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
3 
 
Ejercicio 15. Sincronización de barrera cíclica 
Se pide implementar un programa concurrente con las siguientes características: 
 El programa tendrá 4 procesos que escriben una única letra indefinidamente. Un proceso escribirá 
la letra A, otra la B, otra la C y otro la D. 
 Cada proceso escribe su letra y espera a que los demás hayan escrito también su letra para poder 
continuar. 
 Uno de los procesos tiene que escribir un guión – después de que todos hayan escrito su letra y 
antes de que empiecen a escribirlas de nuevo. 
 La implementación se realizará con semáforos. 
La salida por pantalla del programa con 4 procesos sería: 
 ACDB-BACD-DCBA-ABCD-BCAD … 
Ejercicio 16. Descarga de múltiples ficheros 
Se desea ampliar el ejercicio de descarga de ficheros (Ejercicio 9). Se requiere que cuando los procesos 
hayan terminado de descargar un fichero, se esperen a que se muestre por pantalla y comiencen a 
descargar un nuevo fichero, así hasta descargar 10 ficheros. La implementación deberá realizarse 
íntegramente con semáforos. 
Ejercicio 17. Filósofos Comilones 
Se pide implementar un programa con semáforos con las siguientes características: 
 5 Filósofos que piensan libremente y comen en una mesa con 5 platos 
 Cada filósofo tiene un plato asignado para él sólo 
 Hay 5 tenedores, uno entre cada par de platos adyacentes 
 
Cada uno de los 5 filósofos está representado por un proceso. Los filósofos tienen el siguiente ciclo de vida: 
 El filósofo inicialmente piensa. 
 Se sienta delante de su plato y toma de uno en uno los tenedores situados a ambos lados de su plato. 
 Come. 
F3
F2
F1F5
F4
T4 T3
T2
T1
T5
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
4 
 
 Cuando finaliza, deja los dos tenedores en su posición original. 
 Todo filósofo que come, en algún momento se harta y termina. 
 Vuelve a empezar. 
Se pide implementar un programa concurrente en Java con SimpleConcurrent que simule la vida de los 
filósofos. Para ello, se deben cumplir las siguientes restricciones: 
 Un filósofo sólo puede comer cuando tenga los dos tenedores. 
 Los tenedores se cogen y se dejan de uno en uno. 
 Dos filósofos no pueden tener el mismo tenedor simultáneamente (Exclusión Mutua de acceso al 
tenedor). 
 Si varios filósofos tratan de comer al mismo tiempo, uno de ellos debe conseguirlo (Ausencia 
Interbloqueo).
 Si un filósofo desea comer y tiene competencia, en algún momento lo deberá poder hacerlo 
(Ausencia de Inanición). 
 En ausencia de competencia, un filósofo que quiera comer deberá hacerlo sin retrasos innecesarios 
(Ausencia retrasos innecesarios). 
Se propone el siguiente esquema de código para la resolución del ejercicio: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer17_Filosofos_Plantilla { 
 
 public static final int N_FILOSOFOS = 5; 
 
 public static void filosofo(int numFilosofo) { 
 
 while (true) { 
 printlnI("Pensar"); 
 // Obtener tenedores 
 printlnI("Comer"); 
 // Liberar tenedores 
 } 
 } 
 
 public static void main(String[] args) { 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 createThread("filosofo", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
Ejercicio 18. Preguntas cortas sobre semáforos 
18.1) Describa la semántica de los métodos acquire() y release() aplicadas sobre semáforos generales. 
18.2) ¿Cómo se resuelve el acceso exclusivo de un conjunto de procesos a una variable compartida 
mediante semáforos? 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
5 
 
18.3) Complete la siguiente tabla cuando el proceso P ejecuta la operación de la columna izquierda sobre el 
semáforo s. 
Operaciones sobre el 
Semáforo S 
Contador de 
Permisos 
Antes Después 
Lista de Procesos 
Bloquedados 
Antes Después 
s.acquire() 3 Vacía 
s.acquire() 0 P1 
s.release() 1 Vacía 
s.release() 0 Vacía 
s.release() 0 P1, P2 
s.acquire() 0 Vacía 
s.acquire() 0 P1 
s.acquire() 1 Vacía 
s.release() 0 Vacía 
s.release() 0 P1 
s.release() 1 Vacía 
Solución: 
Operaciones sobre el 
Semáforo s 
Contador de 
Permisos 
Antes Después 
Lista de Procesos 
Bloquedados 
Antes Después 
s.acquire() 3 2 Vacía Vacía 
s.acquire() 0 0 P1 P1, P 
s.release() 1 2 Vacía Vacía 
s.release() 0 1 Vacía Vacía 
s.release() 0 0 P1, P2 P1 o P2 
s.acquire() 0 0 Vacía P 
s.acquire() 0 0 P1 P1 y P 
s.acquire() 1 0 Vacía Vacía 
s.release() 0 1 Vacía Vacía 
s.release() 0 0 P1 Vacía 
s.release() 1 2 Vacía Vacía 
 
18.4) ¿Cuántos semáforos se precisan para implantar un buffer de tamaño ilimitado? Razone la respuesta. 
18.5) Con qué valores se debe inicializar un semáforo para: 
a) resolver la sincronización condicional de dos procesos? 
b) resolver la exclusión mutua en el acceso a un recurso de N procesos? 
c) resolver la exclusión mutua generalizada de N procesos a un recurso que soporta K accesos 
simultáneos? 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
6 
 
18.6) ¿Por qué las sentencias s.acquire() y s.release() garantizan la exclusión mutua en el acceso al 
contador de permisos del semáforo s? 
18.7) Escribe el código correspondiente al método puntoSincronización del siguiente programa utilizando 
semáforos, para evitar que alguno de los N procesos comience a ejecutar el procedimiento B antes de que 
alguno de los demás haya finalizado la ejecución del procedimiento A. Incorpora los atributos e 
inicializaciones necesarios. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_7_Enun { 
 
 private static final int NPROCESOS = 3; 
 
 public static void proceso() { 
 print("A"); 
 puntoSincronizacion(); 
 print("B"); 
 } 
 
 private static void puntoSincronizacion() { 
 
 } 
 
 public static void main(String[] args) { 
 createThreads(NPROCESOS, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
18.8) En un semáforo cuyo contador de permisos es mayor que cero, ¿puede haber algún proceso 
bloqueado? Justifique su respuesta 
18.9) Dado el siguiente diagrama de precedencia de tres procesos: 
 
Escriba en Java con SimpleConcurrent usando Semáforos un programa para cumplir el anterior diagrama 
de precedencia. 
18.10) Describe exactamente todas las situaciones que podrían ocurrir en el siguiente programa. Si 
adviertes algún error, escribe una solución que garantice que a x se accede bajo exclusión mutua y que P2 
nunca termina hasta que ambos procesos hayan incrementado la variable x. 
 
A 
B C 
D 
P1 
P2 
P3 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
7 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer18_10_Enun { 
 
 private static int x; 
 private static SimpleSemaphore sincronizacion; 
 private static SimpleSemaphore exclusion; 
 
 public static void p1() { 
 exclusion.acquire(); 
 x++; 
 if (x == 2) { 
 sincronizacion.release(); 
 } 
 exclusion.release(); 
 } 
 
 public static void p2() { 
 exclusion.acquire(); 
 x++; 
 if (x == 1) { 
 sincronizacion.acquire(); 
 } 
 exclusion.release(); 
 } 
 
 public static void main(String[] args) { 
 
 x = 0; 
 sincronizacion = new SimpleSemaphore(0); 
 exclusion = new SimpleSemaphore(1); 
 
 createThread("p1"); 
 createThread("p2"); 
 startThreadsAndWait(); 
 } 
} 
18.11) ¿Hay algún error en el siguiente programa? 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_11_Enun { 
 
 private static int x; 
 private static int y; 
 private static SimpleSemaphore sY; 
 private static SimpleSemaphore sX; 
 
 public static void pA() { 
 sX.acquire(); 
 x++; 
 sY.acquire(); 
 y *= x; 
 sY.release(); 
 sX.release(); 
 } 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
8 
 
 
 public static void pB() { 
 sY.acquire(); 
 y++; 
 sX.acquire(); 
 x *= y; 
 sX.release(); 
 sY.release(); 
 } 
 
 public static void main(String[] args) { 
 
 x = 2; 
 y = 3; 
 
 sX = new SimpleSemaphore(1); 
 sY = new SimpleSemaphore(1); 
 
 createThread("pA"); 
 createThread("pB"); 
 
 startThreadsAndWait(); 
 } 
} 
4.12) Escribe en Java con SimpleConcurrent el programa que cumpla el siguiente diagrama de precedencia 
de procesos: 
 
18.13) El siguiente código trata de implantar una sincronización de barrera para un número de procesos 
determinado por la constante N. 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_13_Enun { 
 
 private static final int N = 3; 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void proceso() { 
 while (true) { 
 print("A"); 
 puntoSincronizacion(); 
 print("B"); 
 } 
 } 
A 
C D 
F 
P1 
P2 
P3 
B 
E 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
9 
 
 
 private static void puntoSincronizacion() { 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos == N) { 
 nProcesos = 0; 
 emNProcesos.release(); //I0 
 for (int i = 0; i < N; i++) { 
 sb.release(); //I1 
 } 
 } else { 
 emNProcesos.release(); 
 sb.acquire(); 
 } 
 } 
 
 public static void main(String[] args) { 
 nProcesos = 0; 
 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThreads(N, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
Explica brevemente por qué no se sincronizan adecuadamente los procesos en la sincronización
de barrera 
en este ejemplo. Observa las instrucciones etiquetadas con I0 e I1. 
18.14) Dados los dos siguientes procesos concurrentes: 
 public static void p1(){ 
 sX.acquire(); 
 sY.acquire(); 
 x += y; 
 sY.release(); 
 sX.release(); 
 } 
 
 public static void p2(){ 
 sY.acquire(); 
 sX.acquire(); 
 y += x; 
 sX.release(); 
 sY.release(); 
 } 
Si inicialmente sX y sY tienen el contador de permisos a valor 1, y X e Y tienen valor 1, describe todas las 
situaciones que podrían ocurrir. Si adviertes algún error, corrígelo. 
18.15) Sea el siguiente programa en el que N procesos compiten por utilizar un cierto recurso, de manera 
que como máximo K de estos procesos puedan usar el recurso concurrentemente. ¿Cómo se denomina 
este problema clásico? Escriba en Java con SimpleConcurrent una solución a este problema usando 
semáforos. 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
10 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_15_Enun { 
 
 private static final int N_PROCESOS = 4; 
 private static final int K = 2; 
 
 public static void p() { 
 while(true){ 
 // Obtener recurso 
 // Usar recurso 
 // Liberar recurso 
 // Hacer otras cosas 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(N_PROCESOS,"p"); 
 startThreadsAndWait(); 
 } 
} 
18.16) Dibuje el diagrama de precedencia del siguiente programa. 
package exercises.semaphores; 
 
import static simpleconcurrent.SimpleConcurrent.*; 
import simpleconcurrent.SimpleSemaphore; 
 
public class Ejer4_16_Enun { 
 
 private static SimpleSemaphore[] sinc = new SimpleSemaphore[2]; 
 
 public static void puntoSinc(int numProc) { 
 
 if (numProc == 0) { 
 sinc[0].release(); 
 sinc[1].release(); 
 } else { 
 if (numProc == 1) { 
 sinc[1].release(); 
 sinc[0].acquire(); 
 } else { 
 sinc[1].acquire(); 
 sinc[1].acquire(); 
 } 
 } 
 } 
 
 public static void proceso(int numProc) { 
 print("A"+numProc+" "); 
 puntoSinc(numProc); 
 print("B"+numProc+" "); 
 } 
 
 
 
 
 
 
 public static void main(String[] args) { 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
11 
 
 sinc[0] = new SimpleSemaphore(0); 
 sinc[1] = new SimpleSemaphore(0); 
 
 for (int i = 0; i < 3; i++) { 
 createThread("proceso", i); 
 } 
 
 startThreadsAndWait(); 
 } 
} 
18.17) Dados el siguiente programa concurrente: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_17_Enun { 
 
 private static SimpleSemaphore s; 
 
 public static void p1(){ 
 print("A"); 
 s.release(); 
 } 
 
 public static void p2(){ 
 s.acquire(); 
 print("B"); 
 s.release(); 
 s.release(); 
 } 
 
 public static void p3(){ 
 s.acquire(); 
 s.acquire(); 
 print("C"); 
 } 
 
 public static void main(String[] args){ 
 
 s = new SimpleSemaphore(0); 
 
 createThread("p1"); 
 createThread("p2"); 
 createThread("p3"); 
 
 startThreadsAndWait(); 
 } 
} 
¿Se puede asegurar que siempre la ejecución de A precede a la de B y la de B precede a la de C? 
Ejercicio 19. Lectores-Escritores 
Se desea implementar un programa concurrente con semáforos con procesos lectores y procesos 
escritores. En este ejercicio vamos a un ver un ejemplo típico de acceso combinado (concurrente y 
exclusivo) a variables compartidas. Este tipo de acceso se tiene en los sistemas gestores de bases de datos. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
12 
 
Los procesos lectores pueden leer información de la base de datos y los procesos escritores pueden 
escribir información en ella. Para simplificar la resolución del ejercicio, las operaciones de los procesos 
lectores y escritores se implementarán como escrituras por pantalla. 
 
La solución se basará en el siguiente esquema: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer19_LectoresEscritores_Plantilla { 
 
 public static void inicioLectura() { } 
 
 public static void finLectura() { } 
 
 public static void inicioEscritura() { } 
 
 public static void finEscritura() { } 
 
 public static void lector() { 
 while(true){ 
 inicioLectura(); 
 println("Leer datos"); 
 finLectura(); 
 println("Procesar datos"); 
 } 
 } 
 
 public static void escritor() { 
 while (true) { 
 println("Generar datos"); 
 inicioEscritura(); 
 println("Escribir datos"); 
 finEscritura(); 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(5, "lector"); 
 createThreads(3, "escritor"); 
 startThreadsAndWait(); 
 } 
} 
B.D
Escritores
Lectores
Leer
Escribir
Leer
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
13 
 
Se pide implementar los métodos inicioLectura(), finLectura(), inicioEscritura(), finEscritura() de forma que 
se cumplan las siguientes propiedades: 
 Cualquier número de lectores puede acceder a la vez a la BD, siempre que no haya escritores 
accediendo. 
 El acceso a la BD de los escritores es exclusivo. Mientras haya algún lector leyendo, ningún escritor 
puede acceder a la BD, pero otros lectores sí. 
 Se puede tener varios escritores trabajando, aunque estos se deberán sincronizar para que la 
escritura se lleve a cabo de uno en uno. 
 Se da prioridad a los escritores. Ningún lector puede acceder a la BD cuando haya escritores que 
desean hacerlo (aunque esto pueda causar inanición de Lectores). 
__MACOSX/Academia/Tema2/._Tema 2.2. Ejercicios.pdf
Academia/Tema2/Tema2.2 - Ejercicios (soluciones).pdf
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
1 
 
 
 
Ejercicios Tema 2. Parte 2 
Concurrencia con Memoria Compartida 
Soluciones 
Los ejercicios de esta hoja sirven para que los alumnos puedan ejercitar sus conocimientos en el tema de la 
sincronización con memoria compartida. 
Ejercicio 13 
Una línea del metro de Madrid está formada por varios tramos de vía que son recorridos secuencialmente 
en un único sentido por diferentes trenes. 
El ciclo de vida de los procesos que controlan los trenes es el siguiente: 
 public static void tren(int numTren) { 
 
 sleepRandom(500); 
 recorrerTramoA(numTren); 
 
 sleepRandom(500); 
 recorrerTramoB(numTren); 
 
 sleepRandom(500); 
 recorrerTramoC(numTren); 
 } 
 
El esquema del programa completo es el siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer13A_Metro_Plantilla { 
 
 private static final int NUM_TRENES = 5; 
 
 public static void tren(int numTren) { 
 
 sleepRandom(500); 
 recorrerTramoA(numTren); 
 
 sleepRandom(500); 
 recorrerTramoB(numTren); 
 
 sleepRandom(500); 
 recorrerTramoC(numTren); 
 } 
 
 private static void recorrerTramoA(int numTren) { 
 printlnI("Entra TA T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TA T" + numTren); 
 } 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
2 
 
 
 private static void recorrerTramoB(int numTren) { 
 printlnI("Entra TB T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TB T" + numTren); 
 } 
 
 private static void recorrerTramoC(int numTren) {
printlnI("Entra TC T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TC T" + numTren); 
 } 
 
 public static void main(String args[]) { 
 for (int i = 0; i < NUM_TRENES; i++) { 
 createThread("tren", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
a) Se pide escribir todo el código de sincronización necesario para garantizar que se cumplan las siguientes 
restricciones: 
• Cada tramo sólo pueda estar ocupado por un tren en cada instante 
• No deben producirse interbloqueos 
• Si un tren puede recorrer un tramo que se encuentra libre, debe hacerlo sin retrasarse 
innecesariamente 
• Los trenes nunca pueden adelantarse unos a otros. 
• La implementación se realizará con semáforos. 
Solución: La idea básica de la solución es poner cada tramo de la vía bajo exclusión mutua. Eso garantiza 
que no hay dos trenes en el mismo tramo de vía. Para evitar los adelantamientos, basta con salir de la 
exclusión mutua del tramo anterior cuando ya se ha entrado en la exclusión mutua del tramo siguiente. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer13A_Metro { 
 
 private static final int NUM_TRENES = 5; 
 private static SimpleSemaphore emTramoA; 
 private static SimpleSemaphore emTramoB; 
 private static SimpleSemaphore emTramoC; 
 
 public static void tren(int numTren) { 
 
 sleepRandom(500); 
 
 emTramoA.acquire(); 
 recorrerTramoA(numTren); 
 
 sleepRandom(500); 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
3 
 
 emTramoB.acquire(); 
 emTramoA.release(); 
 recorrerTramoB(numTren); 
 
 sleepRandom(500); 
 
 emTramoC.acquire(); 
 emTramoB.release(); 
 recorrerTramoC(numTren); 
 
 emTramoC.release(); 
 } 
 
 private static void recorrerTramoA(int numTren) { 
 printlnI("Entra TA T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TA T" + numTren); 
 } 
 
 private static void recorrerTramoB(int numTren) { 
 printlnI("Entra TB T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TB T" + numTren); 
 } 
 
 private static void recorrerTramoC(int numTren) { 
 printlnI("Entra TC T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale TC T" + numTren); 
 } 
 
 
 public static void main(String args[]){ 
 
 emTramoA = new SimpleSemaphore(1); 
 emTramoB = new SimpleSemaphore(1); 
 emTramoC = new SimpleSemaphore(1); 
 
 for(int i=0; i<NUM_TRENES; i++){ 
 createThread("tren", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
 
b) Ahora se pide generalizar la solución anterior para cualquier número de tramos. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer13B_Metro { 
 
 private static final int NUM_TRAMOS = 6; 
 private static final int NUM_TRENES = 3; 
 private static SimpleSemaphore[] emTramos; 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
4 
 
 public static void tren(int numTren) { 
 for (int numTramo = 0; numTramo < NUM_TRAMOS; numTramo++) { 
 
 sleepRandom(500); 
 emTramos[numTramo].acquire(); 
 
 if(numTramo != 0){ 
 emTramos[numTramo-1].release(); 
 } 
 
 recorrerTramo(numTren, numTramo); 
 } 
 
 emTramos[NUM_TRAMOS-1].release(); 
 } 
 
 private static void recorrerTramo(int numTren, int numTramo) { 
 char tramo = (char)('A' + numTramo); 
 printlnI("Entra T"+tramo+" T" + numTren); 
 sleepRandom(500); 
 printlnI("Sale T"+tramo+" T" + numTren); 
 } 
 
 public static void main(String args[]){ 
 
 emTramos = new SimpleSemaphore[NUM_TRAMOS]; 
 for(int i=0; i<NUM_TRAMOS; i++){ 
 emTramos[i] = new SimpleSemaphore(1); 
 } 
 
 for(int i=0; i<NUM_TRENES; i++){ 
 createThread("tren", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
Ejercicio 14. Sincronización de barrera 
La Sincronización de Barrera es una sincronización condicional en la que los procesos tienen que esperar a 
que el resto de procesos lleguen al mismo punto para poder continuar su ejecución. Este tipo de 
sincronización es muy utilizada en programas concurrentes. 
Se pide implementar un programa concurrente con las siguientes características: 
• El programa tendrá N procesos del mismo tipo. 
• Cada proceso escribe la letra ‘A’, luego la ‘B’ y terminan 
• Los procesos tienen que esperar que todos hayan escrito la letra ‘A’ antes de poder escribir la ‘B’ 
• La implementación se realizará con semáforos. 
La idea básica al implementar la sincronización de barrera es usar un contador de procesos que han 
llegado a la barrera. Si el contador es menor que el número total de procesos, entonces el proceso se 
bloquea. Si el contador es igual al número total de procesos, entonces ese proceso desbloquea a los demás 
para que todos puedan proseguir su ejecución. 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
5 
 
1ª Aproximación Incorrecta - Puede provocar interbloqueo (deadlock) porque nProcesos no está bajo 
exclusión mutua y se pueden producir errores al contar 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer14_SincBarrera_Mal1 { 
 
 private static final int NPROCESOS = 3; 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 
 public static void proceso() { 
 print("A"); 
 nProcesos++; 
 if (nProcesos < NPROCESOS) { 
 sb.acquire(); 
 } else { 
 for (int i = 0; i < NPROCESOS - 1; i++) { 
 sb.release(); 
 } 
 } 
 print("B"); 
 } 
 
 public static void main(String[] args) { 
 nProcesos = 0; 
 
 sb = new SimpleSemaphore(0); 
 
 createThreads(NPROCESOS, "proceso"); 
 
 startThreadsAndWait(); 
 } 
} 
2ª Aproximación Incorrecta - Si se deja la consulta del contador fuera de la Exclusión Mutua, puede 
ocurrir que dos procesos hagan release(). 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer14_SincBarrera_Mal2 { 
 
 private static final int NPROCESOS = 3; 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void proceso() { 
 print("A"); 
 
 emNProcesos.acquire(); 
 nProcesos++; 
 emNProcesos.release(); 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
6 
 
 if (nProcesos < NPROCESOS) { 
 sb.acquire(); 
 } else { 
 for (int i = 0; i < NPROCESOS - 1; i++) { 
 sb.release(); 
 } 
 } 
 print("B"); 
 } 
 
 public static void main(String[] args) { 
 nProcesos = 0; 
 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThreads(NPROCESOS, "proceso"); 
 
 startThreadsAndWait(); 
 } 
} 
Solución: Si el proceso no es el último, libera la EM y se bloquea. Si es el último, sale de la EM y 
desbloquea a los demás procesos. 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer14_SincBarrera { 
 
 private static final int NPROCESOS = 3; 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void proceso() { 
 print("A"); 
 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos < NPROCESOS) {
emNProcesos.release(); 
 sb.acquire(); 
 } else { 
 emNProcesos.release(); 
 for (int i = 0; i < NPROCESOS - 1; i++) { 
 sb.release(); 
 } 
 } 
 
 print("B"); 
 } 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
7 
 
 public static void main(String[] args) { 
 
 nProcesos = 0; 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThreads(NPROCESOS, "proceso"); 
 
 startThreadsAndWait(); 
 } 
} 
Ejercicio 15. Sincronización de barrera cíclica 
Se pide implementar un programa concurrente con las siguientes características: 
• El programa tendrá 4 procesos que escriben una única letra indefinidamente. Un proceso escribirá 
la letra A, otra la B, otra la C y otro la D. 
• Cada proceso escribe su letra y espera a que los demás hayan escrito también su letra para poder 
continuar. 
• Uno de los procesos tiene que escribir un guión – después de que todos hayan escrito su letra y 
antes de que empiecen a escribirlas de nuevo. 
• La implementación se realizará con semáforos. 
La salida por pantalla del programa con 4 procesos sería: 
 ACDB-BACD-DCBA-ABCD-BCAD … 
1ª Aproximación Incorrecta La primera aproximación se basa en usar una sincronización de barrera como 
la que hemos visto en el ejercicio 14. 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer15_SincBarreraCicl_Mal { 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void procesoA() { 
 while(true){ 
 print("A"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoB() { 
 while(true){ 
 print("B"); 
 sincronizacion(); 
 } 
 } 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
8 
 
 public static void procesoC() { 
 while(true){ 
 print("C"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoD() { 
 while(true){ 
 print("D"); 
 sincronizacion(); 
 } 
 } 
 
 public static void sincronizacion(){ 
 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos < 4) { 
 emNProcesos.release(); 
 sb.acquire(); 
 } else { 
 
 println("-"); 
 
 nProcesos = 0; 
 emNProcesos.release(); 
 
 for (int i = 0; i < 3; i++) { 
 //sleepRandom(500); Simular condiciones de carrera 
 sb.release(); 
 } 
 } 
 } 
 
 public static void main(String[] args) { 
 
 nProcesos = 0; 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThread("procesoA"); 
 createThread("procesoB"); 
 createThread("procesoC"); 
 createThread("procesoD"); 
 
 startThreadsAndWait(); 
 } 
} 
El problema que tiene esta solución es que es posible que un hilo recién despertado muestre su letra y le de 
tiempo a invocar sb.acquiere() antes de que el hilo despertador haya despertado al resto de sus 
compañeros. En ese caso, como el semáforo es aleatorio, es posible que ese hilo que ya ha hecho su 
trabajo pueda desbloquearse de nuevo, volviendo a realizar su trabajo. Si descomentamos la línea con el 
sleepRandom(500) veremos que la salida será similar a esta: 
CDAB- 
DDDB- 
DDBD- 
BBDB- 
DDBD- 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
9 
 
2ª Aproximación incorrecta: Para solventar los problemas con la aproximación anterior, podríamos 
pensar en que la exclusión mutua del contador de procesos no se libere hasta que el proceso “despertador” 
haya despertado a todos los demás, con eso evitaríamos que un proceso recién despertado pueda invocar 
sb.acquiere() antes de que todos sus compañeros se hayan despertado. Esta aproximación sería la 
siguiente: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer15_SincBarreraCicl_Mal2 { 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void procesoA() { 
 while(true){ 
 print("A"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoB() { 
 while(true){ 
 print("B"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoC() { 
 while(true){ 
 print("C"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoD() { 
 while(true){ 
 print("D"); 
 sincronizacion(); 
 } 
 } 
 
 public static void sincronizacion(){ 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos < 4) { 
 emNProcesos.release(); 
 //sleepRandom(500); Simular condiciones de carrera 
 sb.acquire(); 
 } else { 
 println("-"); 
 nProcesos = 0; 
 for (int i = 0; i < 3; i++) { 
 sb.release(); 
 } 
 emNProcesos.release(); 
 } 
 } 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
10 
 
 
 public static void main(String[] args) { 
 
 nProcesos = 0; 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThread("procesoA"); 
 createThread("procesoB"); 
 createThread("procesoC"); 
 createThread("procesoD"); 
 
 startThreadsAndWait(); 
 } 
} 
Pero esta aproximación tampoco es correcta. El problema está en que desde que un proceso se da cuenta 
de que no es el último (nProcesos < 4) e invoca sb.acquiere() puede pasar mucho tiempo. Y en ese tiempo, 
otro proceso puede haberse bloqueado, despertado y vuelto a bloquear. Este comportamiento se puede 
similar descomentando la sentencia con sleepRandom(500) y la salida será similar a esta: 
CABD- 
BCDB- 
BAAC- 
DCDA- 
BCBA- 
Es muy difícil darse cuenta de que estas soluciones son incorrectas porque si no se intercalan las sentencias 
de sleep en la mayoría de los casos los programas funcionan correctamente. Pero en los casos extremos 
(que forzamos con los sleep) los programas se comportan de forma errónea. En los programas en 
producción, estas situaciones anómalas se suelen producir cuando el sistema está muy cargado, por lo que 
son muy difíciles de resolver. 
Solución: Una de las posibles soluciones consiste en que los hilos que son desbloqueados tengan que 
notificar al hilo despertador que realmente se han desbloqueado y que han salido de la barrera. Cuando el 
hilo despertador recibe todas las notificaciones, entonces libera la exclusión mutua de la barrera para 
comenzar otro ciclo. Para implementar la solución se ha usado un método de la clase SimpleSemaphore 
que permite hacer release de varios permisos en una única llamada: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer15_SincBarreraCicl { 
 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore desbloqueo; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void procesoA() { 
 while (true) { 
 print("A"); 
 sincronizacion(); 
 } 
 } 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
11 
 
 
 public static void procesoB() { 
 while (true) { 
 print("B"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoC() { 
 while (true) { 
 print("C"); 
 sincronizacion(); 
 } 
 } 
 
 public static void procesoD()
{ 
 while (true) { 
 print("D"); 
 sincronizacion(); 
 } 
 } 
 
 public static void sincronizacion() { 
 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos < 4) { 
 emNProcesos.release(); 
 sleepRandom(500); // Simular condiciones de carrera 
 sb.acquire(); 
 desbloqueo.release(); 
 } else { 
 
 println("-"); 
 nProcesos = 0; 
 
 sb.release(3); 
 desbloqueo.acquire(3); 
 emNProcesos.release(); 
 } 
 } 
 
 public static void main(String[] args) { 
 
 nProcesos = 0; 
 sb = new SimpleSemaphore(0); 
 desbloqueo = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThread("procesoA"); 
 createThread("procesoB"); 
 createThread("procesoC"); 
 createThread("procesoD"); 
 
 startThreadsAndWait(); 
 } 
} 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
12 
 
Ejercicio 16. Descarga de múltiples ficheros 
Se desea ampliar el ejercicio de descarga de ficheros (Ejercicio 9). Se requiere que cuando los procesos 
hayan terminado de descargar un fichero, se esperen a que se muestre por pantalla y comiencen a 
descargar un nuevo fichero, así hasta descargar 10 ficheros. La implementación deberá realizarse 
íntegramente con semáforos. 
Solución: 
La solución se consigue implementando una sincronización de barrera cuando se ha descargado un fichero 
completo, de forma que el resto de hilos se esperan para volver a descargar un nuevo fichero. Para ello, 
basta con modificar el método “downloader()” de la solución al Ejercicio 9 para descargar 10 ficheros en 
vez de 1, reinicializando todas las variables cuando todos los hilos han finalizado su trabajo de descarga. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer16_Downloader { 
 
 private static final int N_FRAGMENTOS = 10; 
 private static final int N_HILOS = 3; 
 
 private static volatile int[] fichero = new int[N_FRAGMENTOS]; 
 private static volatile int fragPendiente = 0; 
 private static volatile int hilosTerminados = 0; 
 
 private static SimpleSemaphore emFragPendiente; 
 
 private static SimpleSemaphore emHilosTerminados; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore desbloqueo; 
 
 private static int descargarDatos(int numFragment) { 
 sleepRandom(1000); 
 return numFragment * 2; 
 } 
 
 private static void mostrarFichero() { 
 println("--------------------------------------------------"); 
 print("File = ["); 
 for (int i = 0; i < N_FRAGMENTOS; i++) { 
 print(fichero[i] + ","); 
 } 
 println("]"); 
 } 
 
 
 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
13 
 
 public static void downloader() { 
 
 for (int i = 0; i < 10; i++) { 
 
 descargarFragmentos(); 
 
 emHilosTerminados.acquire(); 
 hilosTerminados++; 
 if (hilosTerminados < N_HILOS) { 
 
 emHilosTerminados.release(); 
 sb.acquire(); 
 desbloqueo.release(); 
 
 } else { 
 
 mostrarFichero(); 
 fragPendiente = 0; 
 hilosTerminados = 0; 
 
 sb.release(N_HILOS-1); 
 desbloqueo.acquire(N_HILOS-1); 
 emHilosTerminados.release(); 
 } 
 } 
 } 
 
 private static void descargarFragmentos() { 
 while (true) { 
 
 emFragPendiente.acquire(); 
 if (fragPendiente == N_FRAGMENTOS) { 
 emFragPendiente.release(); 
 break; 
 } 
 
 int fragDescargar = fragPendiente; 
 fragPendiente++; 
 emFragPendiente.release(); 
 
 println(getThreadName() + ": Descargando fragmento " + fragDescargar); 
 
 int downloadedData = descargarDatos(fragDescargar); 
 
 println(getThreadName() + ": Escribiendo fragmento " + fragDescargar); 
 
 fichero[fragDescargar] = downloadedData; 
 } 
 } 
 
 public static void main(String[] args) { 
 
 emFragPendiente = new SimpleSemaphore(1); 
 
 emHilosTerminados = new SimpleSemaphore(1); 
 sb = new SimpleSemaphore(0); 
 desbloqueo = new SimpleSemaphore(0); 
 
 createThreads(N_HILOS, "downloader"); 
 
 startThreadsAndWait(); 
 } 
} 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
14 
 
Ejercicio 17. Filósofos Comilones 
Se pide implementar un programa con semáforos con las siguientes características: 
• 5 Filósofos que piensan libremente y comen en una mesa con 5 platos 
• Cada filósofo tiene un plato asignado para él sólo 
• Hay 5 tenedores, uno entre cada par de platos adyacentes 
 
Cada uno de los 5 filósofos está representado por un proceso. Los filósofos tienen el siguiente ciclo de vida: 
• El filósofo inicialmente piensa. 
• Se sienta delante de su plato y toma de uno en uno los tenedores situados a ambos lados de su plato. 
• Come. 
• Cuando finaliza, deja los dos tenedores en su posición original. 
• Todo filósofo que come, en algún momento se harta y termina. 
• Vuelve a empezar. 
Se pide implementar un programa concurrente en Java con SimpleConcurrent que simule la vida de los 
filósofos. Para ello, se deben cumplir las siguientes restricciones: 
• Un filósofo sólo puede comer cuando tenga los dos tenedores. 
• Los tenedores se cogen y se dejan de uno en uno. 
• Dos filósofos no pueden tener el mismo tenedor simultáneamente (Exclusión Mutua de acceso al 
tenedor). 
• Si varios filósofos tratan de comer al mismo tiempo, uno de ellos debe conseguirlo (Ausencia 
Interbloqueo). 
• Si un filósofo desea comer y tiene competencia, en algún momento lo deberá poder hacerlo 
(Ausencia de Inanición). 
• En ausencia de competencia, un filósofo que quiera comer deberá hacerlo sin retrasos innecesarios 
(Ausencia retrasos innecesarios). 
Se propone el siguiente esquema de código para la resolución del ejercicio: 
 
F3
F2
F1F5
F4
T4 T3
T2
T1
T5
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
15 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer17_Filosofos_Plantilla { 
 
 public static final int N_FILOSOFOS = 5; 
 
 public static void filosofo(int numFilosofo) { 
 
 while (true) { 
 printlnI("Pensar"); 
 // Obtener tenedores 
 printlnI("Comer"); 
 // Liberar tenedores 
 } 
 } 
 
 public static void main(String[] args) { 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 createThread("filosofo", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
1º Aproximación Incorrecta: La siguiente puede ser la primera aproximación más evidente, pero no es 
correcta. Esta aproximación propone un semáforo por cada tenedor y pone cada tenedor bajo exclusión 
mutua: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer17_Filosofos_Mal { 
 
 public static final int N_FILOSOFOS = 5; 
 
 public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; 
 
 public static void filosofo(int numFilosofo) { 
 
 while(true){ 
 printlnI("Pensar"); 
 
 int tIzq = numFilosofo; 
 int tDer = (numFilosofo+1) % N_FILOSOFOS; 
 
 tenedores[tIzq].acquire(); 
 sleepRandom(500); //Simular interbloqueo 
 tenedores[tDer].acquire(); 
 
 printlnI("Comer"); 
 
 tenedores[tIzq].release(); 
 tenedores[tDer].release(); 
 } 
 } 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
16
public static void main(String[] args) { 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 tenedores[i] = new SimpleSemaphore(1); 
 } 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 createThread("filosofo", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
Esta solución no es correcta porque si todos los filósofos avanzan a la vez, cogen el tenedor de su izquierda 
y se bloquean esperando el de su derecha (interbloqueo). 
Solución 1: Para evitar los problemas de esta solución, podemos considerar que existe un “comedor” en el 
que caben todos los filósofos menos 1. De esta forma, nunca se producirá interbloqueo entre los filósofos 
porque al menos uno de ellos podrá coger los dos tenedores. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer17_Filosofos_1 { 
 
 public static final int N_FILOSOFOS = 5; 
 
 public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; 
 public static SimpleSemaphore comedor; 
 
 public static void filosofo(int numFilosofo) { 
 
 while (true) { 
 printlnI("Pensar"); 
 
 int tIzq = numFilosofo; 
 int tDer = (numFilosofo + 1) % N_FILOSOFOS; 
 
 comedor.acquire(); 
 
 tenedores[tIzq].acquire(); 
 tenedores[tDer].acquire(); 
 
 printlnI("Comer"); 
 
 tenedores[tIzq].release(); 
 tenedores[tDer].release(); 
 
 comedor.release(); 
 
 } 
 } 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
17 
 
 public static void main(String[] args) { 
 
 comedor = new SimpleSemaphore(N_FILOSOFOS - 1); 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 tenedores[i] = new SimpleSemaphore(1); 
 } 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 createThread("filosofo", i); 
 } 
 startThreadsAndWait(); 
 } 
} 
 
Solución 2: Otra posible solución consiste en que uno de los filósofos sea zurdo y empiece cogiendo los 
tenedores por la derecha en vez de por la izquierda. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer17_Filosofos_2 { 
 
 public static final int N_FILOSOFOS = 5; 
 
 public static SimpleSemaphore[] tenedores = new SimpleSemaphore[N_FILOSOFOS]; 
 public static SimpleSemaphore comedor; 
 
 public static void filosofo(int numFilosofo, boolean diestro) { 
 
 while (true) { 
 printlnI("Pensar"); 
 
 int tIzq = numFilosofo; 
 int tDer = (numFilosofo + 1) % N_FILOSOFOS; 
 
 if (diestro) { 
 tenedores[tIzq].acquire(); 
 tenedores[tDer].acquire(); 
 } else { 
 tenedores[tDer].acquire(); 
 tenedores[tIzq].acquire(); 
 } 
 
 printlnI("Comer"); 
 
 tenedores[tIzq].release(); 
 tenedores[tDer].release(); 
 } 
 } 
 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
18 
 
 public static void main(String[] args) { 
 
 for (int i = 0; i < N_FILOSOFOS; i++) { 
 tenedores[i] = new SimpleSemaphore(1); 
 } 
 
 for (int i = 0; i < N_FILOSOFOS - 1; i++) { 
 createThread("filosofo", i, true); 
 } 
 createThread("filosofo", N_FILOSOFOS - 1, false); 
 
 startThreadsAndWait(); 
 } 
} 
 
Ejercicio 18. Preguntas cortas sobre semáforos 
18.1) Describa la semántica de los métodos acquire() y release() aplicadas sobre semáforos generales. 
s.acquire() – Si el número de permisos del semáforo s es igual a cero el proceso se bloquea en el semáforo 
s; si no, se decrementa en una unidad el contador de permisos del semáforo s y el proceso prosigue su 
ejecución. 
s.release () – Si hay procesos bloqueados en el semáforo s se desbloquea a uno de ellos; si no, se 
incrementa en una unidad el contador de permisos del semáforo s. En ambos casos, el proceso prosigue su 
ejecución. 
Ambos métodos son, por definición, instrucciones atómicas. 
18.2) ¿Cómo se resuelve el acceso exclusivo de un conjunto de procesos a una variable compartida 
mediante semáforos? 
Con semáforos se asocia un semáforo a esa variable, y se inicializa con valor 1. Cuando un proceso debe 
acceder a la variable, primero ejecuta una operación acquire() sobre el semáforo para obtener el acceso 
exclusivo sobre ella; después consulta y/o modifica su valor; y finalmente ejecuta una operación release() 
sobre el semáforo para liberarla. 
18.3) Complete la siguiente tabla cuando el proceso P ejecuta la operación de la columna izquierda sobre el 
semáforo s. 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
19 
 
 
Operaciones sobre el 
Semáforo S 
Contador de 
Permisos 
Antes Después 
Lista de Procesos 
Bloquedados 
Antes Después 
s.acquire() 3 Vacía 
s.acquire() 0 P1 
s.release() 1 Vacía 
s.release() 0 Vacía 
s.release() 0 P1, P2 
s.acquire() 0 Vacía 
s.acquire() 0 P1 
s.acquire() 1 Vacía 
s.release() 0 Vacía 
s.release() 0 P1 
s.release() 1 Vacía 
Solución: 
Operaciones sobre el 
Semáforo s 
Contador de 
Permisos 
Antes Después 
Lista de Procesos 
Bloquedados 
Antes Después 
s.acquire() 3 2 Vacía Vacía 
s.acquire() 0 0 P1 P1, P 
s.release() 1 2 Vacía Vacía 
s.release() 0 1 Vacía Vacía 
s.release() 0 0 P1, P2 P1 o P2 
s.acquire() 0 0 Vacía P 
s.acquire() 0 0 P1 P1 y P 
s.acquire() 1 0 Vacía Vacía 
s.release() 0 1 Vacía Vacía 
s.release() 0 0 P1 Vacía 
s.release() 1 2 Vacía Vacía 
 
18.4) ¿Cuántos semáforos se precisan para implantar un buffer de tamaño ilimitado? Razone la respuesta. 
2 semaforos. Uno para la exclusión mutua sobre el buffer de datos y otro para sincronizar a los 
consumidores cuando no hay datos en el buffer. 
18.5) Con qué valores se debe inicializar un semáforo para: 
a) resolver la sincronización condicional de dos procesos? 
b) resolver la exclusión mutua en el acceso a un recurso de N procesos? 
c) resolver la exclusión mutua generalizada de N procesos a un recurso que soporta K accesos 
simultáneos? 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
20 
 
a) 0 b) 1 c) K 
18.6) ¿Por qué las sentencias s.acquire() y s.release() garantizan la exclusión mutua en el acceso al 
contador de permisos del semáforo s? 
Porque, por definición, son instrucciones atómicas 
18.7) Escribe el código correspondiente al método puntoSincronización del siguiente programa utilizando 
semáforos, para evitar que alguno de los N procesos comience a ejecutar el procedimiento B antes de que 
alguno de los demás haya finalizado la ejecución del procedimiento A. Incorpora los atributos e 
inicializaciones necesarios. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_7_Enun { 
 
 private static final int NPROCESOS = 3; 
 
 public static void proceso() { 
 print("A"); 
 puntoSincronizacion(); 
 print("B"); 
 } 
 
 private static void puntoSincronizacion() { 
 
 } 
 
 public static void main(String[] args) { 
 createThreads(NPROCESOS, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
Solución: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_7 { 
 
 private static final int NPROCESOS = 3; 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void proceso() { 
 print("A"); 
 puntoSincronizacion();
print("B"); 
 } 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
21 
 
 private static void puntoSincronizacion() { 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos == NPROCESOS) { 
 for (int i = 0; i < NPROCESOS; i++) { 
 sb.release(); 
 } 
 } 
 emNProcesos.release(); 
 sb.acquire(); 
 } 
 
 public static void main(String[] args) { 
 nProcesos = 0; 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 createThreads(NPROCESOS, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
 
18.8) En un semáforo cuyo contador de permisos es mayor que cero, ¿puede haber algún proceso 
bloqueado? Justifique su respuesta 
NO. Sólo puede haber procesos bloqueados en el semáforo cuando el contador vale 0. 
18.9) Dado el siguiente diagrama de precedencia de tres procesos: 
 
Escriba en Java con SimpleConcurrent usando Semáforos un programa para cumplir el anterior diagrama 
de precedencia. 
Solución: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_9 { 
 
 private static SimpleSemaphore semA; 
 private static SimpleSemaphore semC; 
 private static SimpleSemaphore semD; 
 
 
 
 
 
A 
B C 
D 
P1 
P2 
P3 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
22 
 
 public static void proceso1() { 
 semA.acquire(); 
 print("A"); 
 semC.release(); 
 } 
 
 public static void proceso2() { 
 print("B"); 
 semA.release(); 
 semD.release(); 
 semC.acquire(); 
 semC.acquire(); 
 print("C"); 
 } 
 
 public static void proceso3() { 
 semD.acquire(); 
 print("D"); 
 semC.release(); 
 } 
 
 public static void main(String[] args) { 
 
 semA = new SimpleSemaphore(0); 
 semC = new SimpleSemaphore(0); 
 semD = new SimpleSemaphore(0); 
 
 createThread("proceso1"); 
 createThread("proceso2"); 
 createThread("proceso3"); 
 
 startThreadsAndWait(); 
 } 
} 
 
18.10) Describe exactamente todas las situaciones que podrían ocurrir en el siguiente programa. Si 
adviertes algún error, escribe una solución que garantice que a x se accede bajo exclusión mutua y que P2 
nunca termina hasta que ambos procesos hayan incrementado la variable x. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer18_10_Enun { 
 
 private static int x; 
 private static SimpleSemaphore sincronizacion; 
 private static SimpleSemaphore exclusion; 
 
 public static void p1() { 
 exclusion.acquire(); 
 x++; 
 if (x == 2) { 
 sincronizacion.release(); 
 } 
 exclusion.release(); 
 } 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
23 
 
 public static void p2() { 
 exclusion.acquire(); 
 x++; 
 if (x == 1) { 
 sincronizacion.acquire(); 
 } 
 exclusion.release(); 
 } 
 
 public static void main(String[] args) { 
 
 x = 0; 
 sincronizacion = new SimpleSemaphore(0); 
 exclusion = new SimpleSemaphore(1); 
 
 createThread("p1"); 
 createThread("p2"); 
 startThreadsAndWait(); 
 } 
} 
Solución: 
a) Si P1 entra primero en su sección crítica, incrementa x y termina. Luego entra P2, incrementa x y 
termina. b) Si P2 entra primero, entonces incrementa x, ejecuta sincronizacion.acquire() y se bloquea. 
Entonces P1 nunca podrá entrar en su sección crítica. Por tanto, se produce un interbloqueo de P1 y P2. La 
solución consiste en que P2 se bloquee fuera de su sección crítica, por ejemplo: 
 public static void p2() { 
 exclusion.acquire(); 
 x++; 
 if (x == 1) { 
 exclusion.release(); 
 sincronizacion.acquire(); 
 } else { 
 exclusion.release(); 
 } 
 } 
Aunque otra solución más sencilla para cumplir con los requisitos del programa puede ser la siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer18_10_2 { 
 
 private static int x; 
 private static SimpleSemaphore sincronizacion; 
 private static SimpleSemaphore exclusion; 
 
 public static void p1() { 
 exclusion.acquire(); 
 x++; 
 exclusion.release(); 
 sincronizacion.release(); 
 } 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
24 
 
 public static void p2() { 
 exclusion.acquire(); 
 x++; 
 exclusion.release(); 
 sincronizacion.acquire(); 
 } 
 
 public static void main(String[] args) { 
 
 x = 0; 
 sincronizacion = new SimpleSemaphore(0); 
 exclusion = new SimpleSemaphore(1); 
 
 createThread("p1"); 
 createThread("p2"); 
 startThreadsAndWait(); 
 } 
} 
18.11) ¿Hay algún error en el siguiente programa? 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_11_Enun { 
 
 private static int x; 
 private static int y; 
 private static SimpleSemaphore sY; 
 private static SimpleSemaphore sX; 
 
 public static void pA() { 
 sX.acquire(); 
 x++; 
 sY.acquire(); 
 y *= x; 
 sY.release(); 
 sX.release(); 
 } 
 
 public static void pB() { 
 sY.acquire(); 
 y++; 
 sX.acquire(); 
 x *= y; 
 sX.release(); 
 sY.release(); 
 } 
 
 public static void main(String[] args) { 
 x = 2; 
 y = 3; 
 sX = new SimpleSemaphore(1); 
 sY = new SimpleSemaphore(1); 
 
 createThread("pA"); 
 createThread("pB"); 
 startThreadsAndWait(); 
 } 
} 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
25 
 
Si el proceso pA ejecuta su primer acquire(), y antes de que ejecute su segundo acquire(), el proceso B 
ejecuta su primer acquire() (o viceversa) se produce un interbloqueo entre ambos. 
4.12) Escribe en Java con SimpleConcurrent el programa que cumpla el siguiente diagrama de precedencia 
de procesos: 
 
Solución: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_12 { 
 
 private static SimpleSemaphore semD; 
 private static SimpleSemaphore semF; 
 
 public static void proceso1() { 
 print("A"); 
 semD.release(); 
 print("B"); 
 } 
 
 public static void proceso2() { 
 print("C"); 
 semF.release(); 
 semD.acquire(); 
 print("D"); 
 } 
 
 public static void proceso3() { 
 print("E"); 
 semF.acquire(); 
 semF.acquire(); 
 print("F"); 
 } 
 
 public static void main(String[] args) { 
 
 semD = new SimpleSemaphore(0); 
 semF = new SimpleSemaphore(0); 
 
 createThread("proceso1"); 
 createThread("proceso2"); 
 createThread("proceso3"); 
 
 startThreadsAndWait(); 
 } 
A 
C D 
F 
P1 
P2 
P3 
B 
E 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
26 
 
} 
18.13) El siguiente código trata de implantar una sincronización de barrera para un número de procesos 
determinado por la constante N. 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_13_Enun { 
 
 private static final int N = 3; 
 private static volatile int nProcesos; 
 private static SimpleSemaphore sb; 
 private static SimpleSemaphore emNProcesos; 
 
 public static void proceso() { 
 while
(true) { 
 print("A"); 
 puntoSincronizacion(); 
 print("B"); 
 } 
 } 
 
 private static void puntoSincronizacion() { 
 emNProcesos.acquire(); 
 nProcesos++; 
 if (nProcesos == N) { 
 nProcesos = 0; 
 emNProcesos.release(); //I0 
 for (int i = 0; i < N; i++) { 
 sb.release(); //I1 
 } 
 } else { 
 emNProcesos.release(); 
 sb.acquire(); 
 } 
 } 
 
 public static void main(String[] args) { 
 nProcesos = 0; 
 
 sb = new SimpleSemaphore(0); 
 emNProcesos = new SimpleSemaphore(1); 
 
 createThreads(N, "proceso"); 
 startThreadsAndWait(); 
 } 
} 
Explica brevemente por qué no se sincronizan adecuadamente los procesos en la sincronización de barrera 
en este ejemplo. Observa las instrucciones etiquetadas con I0 e I1. 
El último proceso que llega al punto de sincronización hace N release(), cuando sólo hay N-1 procesos 
bloqueados en el semáforo, por lo que en la siguiente iteración el primer proceso que llegue al punto de 
sincronización se encuentra el contador del semáforo con valor 1 y no se bloquea esperando a que llegue el 
último. 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
27 
 
Pero además hay otro problema: la liberación de la exclusión mutua en la instrucción I0 permite que un 
proceso que se desbloquee como resultado de un release() en I1 se adelante a los demás, y antes de que se 
puedan desbloquear todos, entre de nuevo en la sección crítica, invoque acquire(), y se desbloquee como 
resultado del siguiente adquire() en I1. Lo cual puede producir la inanición de aquellos procesos que no 
pueden desbloquearse porque el proceso que se ha adelantado se desbloquea en su lugar. 
18.14) Dados los dos siguientes procesos concurrentes: 
 public static void p1(){ 
 sX.acquire(); 
 sY.acquire(); 
 x += y; 
 sY.release(); 
 sX.release(); 
 } 
 
 public static void p2(){ 
 sY.acquire(); 
 sX.acquire(); 
 y += x; 
 sX.release(); 
 sY.release(); 
 } 
Si inicialmente sX y sY tienen el contador de permisos a valor 1, y X e Y tienen valor 1, describe todas las 
situaciones que podrían ocurrir. Si adviertes algún error, corrígelo. 
a) Si P1 ejecuta primero sus dos acquire(), asigna 2 a X. Cuando P1 ejecute sus dos release(), entonces P2 
podrá asignar 3 a Y, y los dos procesos terminarán correctamente. 
b) Si P2 ejecuta primero sus dos acquire(), asigna 2 a Y. Cuando P2 ejecute sus dos release(), entonces P2 
podrá asignar 3 a X, y los dos procesos terminarán correctamente. 
c) Si P1 o P2 ejecuta su primer acquire() y después el otro proceso ejecuta el suyo, hay interbloqueo de los 
dos procesos al ejecutar sus segundos acquire(). La solución consiste en intercambiar el orden de las 
instrucciones acquire() en uno de los procesos. 
18.15) Sea el siguiente programa en el que N procesos compiten por utilizar un cierto recurso, de manera 
que como máximo K de estos procesos puedan usar el recurso concurrentemente. ¿Cómo se denomina 
este problema clásico? Escriba en Java con SimpleConcurrent una solución a este problema usando 
semáforos. 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer18_15_Enun { 
 
 private static final int N_PROCESOS = 4; 
 private static final int K = 2; 
 
 public static void p() { 
 while(true){ 
 // Obtener recurso 
 // Usar recurso 
 // Liberar recurso 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
28 
 
 // Hacer otras cosas 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(N_PROCESOS,"p"); 
 startThreadsAndWait(); 
 } 
} 
 
Solución: 
 Este problema se denomina exclusión mutua generalizada. Una solución usando semáforos es la 
siguiente: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer18_15 { 
 
 private static final int N_PROCESOS = 4; 
 private static final int K = 2; 
 
 private static SimpleSemaphore emGen; 
 
 public static void p() { 
 while(true){ 
 emGen.acquire(); 
 // Usar recurso 
 emGen.release(); 
 // Hacer otras cosas 
 } 
 } 
 
 public static void main(String[] args) { 
 emGen = new SimpleSemaphore(K); 
 createThreads(N_PROCESOS,"p"); 
 startThreadsAndWait(); 
 } 
} 
18.16) Dibuje el diagrama de precedencia del siguiente programa. 
package exercises.semaphores; 
 
import static simpleconcurrent.SimpleConcurrent.*; 
import simpleconcurrent.SimpleSemaphore; 
 
public class Ejer4_16_Enun { 
 
 private static SimpleSemaphore[] sinc = new SimpleSemaphore[2]; 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
29 
 
 
 
 
 public static void puntoSinc(int numProc) { 
 
 if (numProc == 0) { 
 sinc[0].release(); 
 sinc[1].release(); 
 } else { 
 if (numProc == 1) { 
 sinc[1].release(); 
 sinc[0].acquire(); 
 } else { 
 sinc[1].acquire(); 
 sinc[1].acquire(); 
 } 
 } 
 } 
 
 public static void proceso(int numProc) { 
 print("A"+numProc+" "); 
 puntoSinc(numProc); 
 print("B"+numProc+" "); 
 } 
 
 public static void main(String[] args) { 
 sinc[0] = new SimpleSemaphore(0); 
 sinc[1] = new SimpleSemaphore(0); 
 
 for (int i = 0; i < 3; i++) { 
 createThread("proceso", i); 
 } 
 
 startThreadsAndWait(); 
 } 
} 
 
 
 
18.17) Dados el siguiente programa concurrente: 
package ejercicio; 
 
import es.sidelab.sc.SimpleSemaphore; 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
A1
A2
A3
B1
B2
B3
P1
P2
P3
A1
A2
A3
A1
A2
A3
B1
B2
B3
B1
B2
B3
P1
P2
P3
P1
P2
P3
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
30 
 
public class Ejer18_17_Enun { 
 
 
 private static SimpleSemaphore s; 
 
 public static void p1(){ 
 print("A"); 
 s.release(); 
 } 
 
 public static void p2(){ 
 s.acquire(); 
 print("B"); 
 s.release(); 
 s.release(); 
 } 
 
 public static void p3(){ 
 s.acquire(); 
 s.acquire(); 
 print("C"); 
 } 
 
 public static void main(String[] args){ 
 
 s = new SimpleSemaphore(0); 
 
 createThread("p1"); 
 createThread("p2"); 
 createThread("p3"); 
 
 startThreadsAndWait(); 
 } 
} 
¿Se puede asegurar que siempre la ejecución de A precede a la de B y la de B precede a la de C? 
No. Si primero se ejecuta la primera instrucción de P3, éste queda bloqueado en el semáforo S; si luego se 
ejecutan todas las instrucciones de P1, P3 queda en estado de preparado; si se ejecuta la segunda 
instrucción de P3, éste queda de nuevo bloqueado en el semáforo S; el único proceso en estado de 
preparado será P2, y quedará también bloqueado en cuanto ejecute su primera instrucción. Por tanto, se 
produce interbloqueo y no se puede asegurar que B precede a C. 
Ejercicio 19. Lectores-Escritores 
Se desea implementar un programa concurrente con semáforos con procesos lectores y procesos 
escritores. En este ejercicio vamos a un ver un ejemplo típico de acceso combinado (concurrente y 
exclusivo) a variables compartidas. Este tipo de acceso se tiene en los sistemas gestores de bases de datos. 
Los procesos lectores pueden leer información de la base de datos y los procesos escritores pueden 
escribir información en ella. Para simplificar la resolución del ejercicio, las operaciones de los procesos 
lectores y escritores se implementarán como escrituras por pantalla. 
 Programación Concurrente
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
31 
 
 
La solución se basará en el siguiente esquema: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
 
public class Ejer19_LectoresEscritores_Plantilla { 
 
 public static void inicioLectura() { } 
 
 public static void finLectura() { } 
 
 public static void inicioEscritura() { } 
 
 public static void finEscritura() { } 
 
 public static void lector() { 
 while(true){ 
 inicioLectura(); 
 println("Leer datos"); 
 finLectura(); 
 println("Procesar datos"); 
 } 
 } 
 
 public static void escritor() { 
 while (true) { 
 println("Generar datos"); 
 inicioEscritura(); 
 println("Escribir datos"); 
 finEscritura(); 
 } 
 } 
 
 public static void main(String[] args) { 
 createThreads(5, "lector"); 
 createThreads(3, "escritor"); 
 startThreadsAndWait(); 
 } 
} 
Se pide implementar los métodos inicioLectura(), finLectura(), inicioEscritura(), finEscritura() de forma que 
se cumplan las siguientes propiedades: 
• Cualquier número de lectores puede acceder a la vez a la BD, siempre que no haya escritores 
accediendo. 
B.D
Escritores
Lectores
Leer
Escribir
Leer
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
32 
 
• El acceso a la BD de los escritores es exclusivo. Mientras haya algún lector leyendo, ningún escritor 
puede acceder a la BD, pero otros lectores sí. 
• Se puede tener varios escritores trabajando, aunque estos se deberán sincronizar para que la 
escritura se lleve a cabo de uno en uno. 
• Se da prioridad a los escritores. Ningún lector puede acceder a la BD cuando haya escritores que 
desean hacerlo (aunque esto pueda causar inanición de Lectores). 
Solución: 
Para implementar una solución, nos basamos en los siguientes principios de funcionamiento: 
• Cuando un lector intenta entrar en la BD, primero mira para ver si hay algún escritor en ella 
(trabajando) o algún escritor a la espera de entrar. Si no hay ningún escritor trabajando o a la 
espera, entonces entra el lector. En caso contrario, se bloquea a la espera de que terminen los 
escritores. 
• Cuando un escritor intenta entrar en la BD, sólo mira por si hay un lector en ella (trabajando), pero 
no mira si hay algún lector a la espera, porque los escritores tienen prioridad sobre los lectores. Si 
no hay ningún lector trabajando, entonces entra el escritor. En caso contrario, se bloque a la 
espera de que terminen los escritores. 
• Cuando un lector finaliza el acceso a la base de datos y es el último lector trabajando, desbloquea a 
los escritores que estuvieran esperando. 
• Cuando un escritor finaliza el acceso a la base de datos y es el último escritor trabajando, 
desbloquea a los lectores que estuvieran esperando. 
Con estos principios básicos de funcionamiento, la solución sería: 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer19_LectoresEscritores_1 { 
 
 // Número de escritores que han conseguido el acceso a la BD 
 private static int escritoresBD = 0; 
 
 // Número de lectores que han conseguido el acceso a la BD 
 private static int lectoresBD = 0; 
 
 // Lectores esperando a que finalicen los escritores 
 private static int lectoresEspera; 
 
 // Escritores esperando a que finalicen los lectores 
 private static int escritoresEspera; 
 
 // Exclusión mutura de las variables de control 
 private static SimpleSemaphore emControl; 
 
 // Exclusión mutura para el acceso a los escritores 
 private static SimpleSemaphore emEscritura; 
 
 // Bloqueo de los lectores cuando hay escritores 
 private static SimpleSemaphore esperaFinEscritores; 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
33 
 
 // Bloqueo de los escritores cuando hay lectores 
 private static SimpleSemaphore esperaFinLectores; 
 
 public static void inicioLectura() { 
 emControl.acquire(); 
 printlnI("inicioLectura"); 
 if (escritoresBD == 0 && escritoresEspera == 0) { 
 lectoresBD++; 
 printlnI("lectTrab="+lectoresBD); 
 emControl.release(); 
 } else { 
 lectoresEspera++; 
 printlnI("lectEspera="+lectoresEspera); 
 emControl.release(); 
 esperaFinEscritores.acquire(); 
 } 
 } 
 
 public static void finLectura() { 
 emControl.acquire(); 
 lectoresBD--; 
 printlnI("finLectura"); 
 printlnI("lectTrab="+lectoresBD); 
 if (lectoresBD == 0) { 
 printlnI("escriEspera="+escritoresEspera); 
 for(int i=0; i<escritoresEspera; i++) { 
 escritoresBD++; 
 esperaFinLectores.release(); 
 } 
 escritoresEspera = 0; 
 } 
 emControl.release(); 
 } 
 
 public static void inicioEscritura() { 
 emControl.acquire(); 
 printlnI("inicioEscritura"); 
 if (lectoresBD == 0) { 
 escritoresBD++; 
 printlnI("escTrab="+escritoresBD); 
 emControl.release(); 
 } else { 
 escritoresEspera++; 
 printlnI("escEspera="+escritoresEspera); 
 emControl.release(); 
 esperaFinLectores.acquire(); 
 } 
 
 emEscritura.acquire(); 
 printlnI("Escribiendo..."); 
 } 
 
 
 
 
 
 
 
 
 
 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
34 
 
 public static void finEscritura() { 
 printlnI("FinEscribiendo..."); 
 emEscritura.release(); 
 
 emControl.acquire(); 
 escritoresBD--; 
 printlnI("finEscritura"); 
 printlnI("escriTrab="+escritoresBD); 
 if (escritoresBD == 0) { 
 printlnI("lectEspera="+lectoresEspera); 
 for(int i=0; i<lectoresEspera; i++){ 
 lectoresBD++; 
 esperaFinEscritores.release(); 
 } 
 lectoresEspera=0; 
 } 
 emControl.release(); 
 } 
 
 public static void lector() { 
 while (true) { 
 inicioLectura(); 
 printlnI("Leer datos"); 
 sleepRandom(300); 
 finLectura(); 
 printlnI("Procesar datos"); 
 sleepRandom(500); 
 } 
 } 
 
 public static void escritor() { 
 while (true) { 
 printlnI("Generar datos"); 
 sleepRandom(2000); 
 inicioEscritura(); 
 printlnI("Escribir datos"); 
 sleepRandom(500); 
 finEscritura(); 
 } 
 } 
 
 public static void main(String[] args) { 
 
 emControl = new SimpleSemaphore(1); 
 emEscritura = new SimpleSemaphore(1); 
 esperaFinEscritores = new SimpleSemaphore(0); 
 esperaFinLectores = new SimpleSemaphore(0); 
 
 createThreads(5, "lector"); 
 createThreads(3, "escritor"); 
 startThreadsAndWait(); 
 } 
} 
 
No obstante, hay otras soluciones posibles. En este caso se presenta otra solución que utiliza el concepto 
de procesos activos y procesos trabajando para representar de otra forma los procesos que quieren entrar 
en la base de datos frente a los que realmente han entrado. El código sería el siguiente: 
 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
35 
 
package ejercicio; 
 
import static es.sidelab.sc.SimpleConcurrent.*; 
import es.sidelab.sc.SimpleSemaphore; 
 
public class Ejer19_LectoresEscritores_2 { 
 
 // Número de escritores que han iniciado inicioEscritura() 
 // Están activos hasta que finalizan finEscritura() 
 private static int escritoresActivos = 0; 
 
 // Número de escritores que han conseguido el acceso 
 // a la BD (Han terminado inicioEscritura()) 
 // Están trabajando hasta que finalizan finEscritura() 
 // Siempre se cumple escritoresActivos >= escritoresTrabajando 
 private static int escritoresBD = 0; 
 
 // Número
de lectores que han iniciado inicioLectura() 
 // Están activos hasta que finalizan finLectura() 
 // Un lector activo puede estar esperando a obtener el 
 // acceso a los datos, que se le concederá cuando no 
 // haya escritores activos 
 private static int lectoresActivos = 0; 
 
 // Número de lectores que han conseguido el acceso a 
 // la BD (Han terminado inicioLectura()) 
 // Están trabajando hasta que finalizan finLectura() 
 // Siempre se cumple lectoresActivos >= lectoresTrabajando 
 private static int lectoresBD = 0; 
 
 // Exclusión mutura de las variables de control 
 private static SimpleSemaphore emControl; 
 
 // Exclusión mutura para el acceso a los escritores 
 private static SimpleSemaphore emEscritura; 
 
 // Bloqueo de los lectores cuando hay escritores 
 private static SimpleSemaphore esperaFinEscritores; 
 
 // Bloqueo de los escritores cuando hay lectores 
 private static SimpleSemaphore esperaFinLectores; 
 
 public static void inicioLectura() { 
 emControl.acquire(); 
 lectoresActivos++; 
 if (escritoresBD == 0 && escritoresActivos == 0) { 
 lectoresBD++; 
 emControl.release(); 
 } else { 
 emControl.release(); 
 esperaFinEscritores.acquire(); 
 } 
 } 
 
 
 
 
 
 
 
 
 public static void finLectura() { 
 emControl.acquire(); 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
36 
 
 lectoresActivos--; 
 lectoresBD--; 
 if (lectoresBD == 0) { 
 while (escritoresActivos > escritoresBD) { 
 escritoresBD++; 
 esperaFinLectores.release(); 
 } 
 } 
 emControl.release(); 
 } 
 
 public static void inicioEscritura() { 
 emControl.acquire(); 
 escritoresActivos++; 
 if (lectoresBD == 0) { 
 escritoresBD++; 
 emControl.release(); 
 } else { 
 emControl.release(); 
 esperaFinLectores.acquire(); 
 } 
 
 emEscritura.acquire(); 
 } 
 
 public static void finEscritura() { 
 emEscritura.release(); 
 
 emControl.acquire(); 
 escritoresActivos--; 
 escritoresBD--; 
 if (escritoresBD == 0) { 
 while (lectoresActivos > lectoresBD) { 
 lectoresBD++; 
 esperaFinEscritores.release(); 
 } 
 } 
 emControl.release(); 
 } 
 
 public static void lector() { 
 while (true) { 
 inicioLectura(); 
 println("Leer datos"); 
 finLectura(); 
 println("Procesar datos"); 
 } 
 } 
 
 public static void escritor() { 
 while (true) { 
 println("Generar datos"); 
 inicioEscritura(); 
 println("Escribir datos"); 
 finEscritura(); 
 } 
 } 
 
 
 
 
 public static void main(String[] args) { 
 
 Programación Concurrente 
3º Grado en Ingeniería de Computadores 
 
Concurrencia con Memoria Compartida 
 
 
37 
 
 emControl = new SimpleSemaphore(1); 
 emEscritura = new SimpleSemaphore(1); 
 esperaFinEscritores = new SimpleSemaphore(0); 
 esperaFinLectores = new SimpleSemaphore(0); 
 
 createThreads(5, "lector"); 
 createThreads(3, "escritor"); 
 startThreadsAndWait(); 
 } 
} 
 
 
 
 
 
 
__MACOSX/Academia/Tema2/._Tema2.2 - Ejercicios (soluciones).pdf
Academia/Tema5/Tema 5.4 - Concurrencia y orientación a objetos UPDATED.pdf
Programación	
  
Concurrente	
  en	
  Java
Programación	
  Concurrente	
  – Tema	
  5
Miguel	
  Ángel	
  Rodríguez	
  García
Carlos	
  Grima
Lucía	
  Serrano	
  Luján
Universidad	
  Rey	
  Juan	
  Carlos
Curso	
  2017	
  -­‐ 2018
Concurrencia	
  y	
  orientación	
  a	
  
objetos
Programación	
  Concurrente	
  en	
  Java	
  -­‐
Tema	
  5.4
3
PROGRAMACIÓN CONCURRENTE EN JAVA
Datos	
  compartidos	
  entre	
  hilos
Objetos	
  compartidos	
  entre	
  hilos
4
PROGRAMACIÓN CONCURRENTE EN JAVA
Datos	
  compartidos	
  entre	
  hilos
Objetos	
  compartidos	
  entre	
  hilos
5
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread	
  1 CPU	
  1cache
CUANDO	
  CREAMOS	
  UN	
  HILO….
Datos	
  compartidos	
  entre	
  hilos
Datos	
  compartidos	
  entre	
  hilos
6
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread	
  1 CPU	
  1cache
CUANDO	
  CREAMOS	
  MÁS	
  HILOS….
CPU	
  2
Thread	
  2 CPU	
  2cache
Datos	
  compartidos	
  entre	
  hilos
7
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread	
  1 CPU	
  1cache
CUANDO	
  CREAMOS	
  MÁS	
  HILOS….
CPU	
  2
Thread	
  2 CPU	
  2cache
Memoria	
  principal
Datos	
  compartidos	
  entre	
  hilos
• Dos	
  o	
  más	
  hilos	
  tienen	
  acceso	
  a	
  una	
  variable:
public class objetoCompartido{	
  
public int contador	
  =	
  0;	
  
}
8
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread	
  1 contador =	
  7	
  
contador =	
  0
cache cacheCPU	
  2
Thread	
  2 contador =	
  0	
  
cache
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
• Los	
  procesadores	
  utilizan	
  memoria	
  cache	
  y	
  
registros	
  para	
  almacenar	
  los	
  valores	
  de	
  las	
  
variables	
  
•En	
  sistemas	
  de	
  varios	
  procesadores	
  (o	
  núcleos),	
  es	
  
habitual	
  que	
  exista	
  una	
  cache	
  por	
  cada	
  procesador	
  
•¿Qué	
  ocurre	
  con	
  las	
  variables	
  compartidas	
  si	
  dos	
  
procesos	
  se	
  ejecutan	
  cada	
  uno	
  en	
  un	
  procesador?	
  
•¿Cuándo	
  se	
  sincronizan	
  las	
  cachés?
•¿Cuándo	
  son	
  visibles	
  los	
  cambios	
  de	
  las	
  variables	
  
por	
  todos	
  los	
  hilos?	
  
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
•Para	
  un	
  compilador	
  es	
  imposible	
  determinar	
  
qué	
  atributos	
  se	
  comparten	
  entre	
  hilos	
  y	
  
cuales	
  no	
  
•No	
  es	
  eficiente	
  sincronizar	
  los	
  valores	
  de	
  
todos	
  los	
  atributos	
  cada	
  vez	
  que	
  se	
  hace	
  una	
  
escritura	
  en	
  ellos	
  por	
  si	
  otro	
  procesador	
  los	
  
usa	
  
•Existe	
  un	
  problema	
  de	
  visibilidad	
  
•¿Cómo	
  se	
  resuelve	
  el	
  problema?	
  
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
•El	
   desarrollador	
   tiene	
   que	
   marcar	
  
explícitamente	
  aquellos	
  atributos	
  compartidos	
  
entre	
  hilos	
  que	
  pueden	
  cambiar	
  de	
  valor	
  
•Existen	
  dos	
  formas	
  de	
  hacerlo:	
  
•Anadir	
  el	
  modificador	
  volatile	
  
•Sincronizar	
   los	
   hilos	
   usando	
   alguna	
   herramienta	
  
de	
  sincronización	
  (semáforos,	
  etc...)	
  desde	
  que	
  se	
  
escribe	
  el	
  valor	
  hasta	
  que	
  se	
  lee	
  el	
  valor	
  (exclusión	
  
mutua,	
  sincronización	
  condicional...)	
  
Datos	
  compartidos	
  entre	
  hilos
• Dos	
  o	
  más	
  hilos	
  tienen	
  acceso	
  a	
  una	
  variable:
public class objetoCompartido{	
  
public volatile int contador	
  =	
  0;	
  
}
12
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread 1
lee	
  contador	
  à
7
volatile	
  contador =	
  7
cache cacheCPU	
  2
Thread 2
lectura	
  de	
  
contador	
  à 7
cache
Garantiza	
  la	
  visibilidad
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
•El	
  sistema	
  se	
  asegura	
  de	
  que	
  un	
  hilo	
  lea	
  el	
  
último	
  valor	
  de	
  un	
  atributo	
  compartido	
  si...	
  
• ...	
  el	
  atributo	
  está	
  marcado	
  como	
  volatile	
  o...	
  
• ...	
  el	
  hilo	
  ha	
  usado	
  alguna	
  herramienta	
  de	
  
sincronización	
  (semáforo...)	
  antes	
  de	
  leer	
  el	
  
atributo	
  
Datos	
  compartidos	
  entre	
  hilos
• Dos	
  o	
  más	
  hilos	
  tienen	
  acceso	
  a	
  una	
  variable:
public class objetoCompartido{	
  
public volatile int contador	
  =	
  0;	
  
}
14
PROGRAMACIÓN CONCURRENTE EN JAVA
CPU	
  1
Thread	
  1 contador =	
  0+1
volatile	
  contador =	
  0
cache cacheCPU	
  2
Thread
2 contador =	
  0+1
cache
¡¡¡Excepción!!!
cuando	
  dos	
  hilos	
  
leen	
  y	
  escribe	
  
sobre	
  la	
  variable	
  
volatile
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
•¿Qué	
  ocurre	
  si	
  un	
  atributo	
  es	
  compartido	
  entre	
  
varios	
  hilos	
  y	
  no	
  se	
  ha	
  marcado	
  como	
  volatile	
  y	
  
no	
  se	
  usa	
  ninguna	
  herramienta	
  de	
  
sincronización	
  entre	
  los	
  procesos?	
  
•Es	
  posible	
  que	
  un	
  hilo	
  lea	
  un	
  valor	
  antiguo	
  (el	
  que	
  
estaba	
  en	
  la	
  caché	
  de	
  ese	
  proceso)	
  y	
  no	
  el	
  ultimo	
  
valor	
  del	
  atributo	
  
•Es	
  posible	
  que	
  el	
  compilador	
  o	
  la	
  JVM	
  elimine	
  la	
  
variable	
  si	
  puede	
  optimizar	
  el	
  código	
  
considerando	
  que	
  sólo	
  un	
  hilo	
  accede	
  a	
  ella	
  
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
• ¿Siempre	
  hay	
  que	
  usar	
  volatile	
  para	
  variables	
  
compartidas	
  entre	
  hilos?	
  
•No,	
  no	
  es	
  necesario	
  utilizar	
  volatile	
  si	
  el	
  hilo	
  que	
  escribe	
  
la	
  variable	
  se	
  sincroniza	
  con	
  el	
  hilo	
  que	
  lee	
  la	
  variable	
  
(usando	
  herramientas	
  de	
  sincronización)	
  
•Por	
  ejemplo:	
  
•�	
   Si	
  los	
  atributos	
  compartidos	
  están	
  bajo	
  exclusión	
  
mutua	
  con	
  un	
  semáforo,	
  se	
  garantiza	
  que	
  se	
  leerán	
  los	
  
valores	
  correctos	
  (sin	
  usar	
  volatile)	
  
•�	
   Si	
  un	
  proceso	
  escribe	
  un	
  atributo	
  y	
  desbloquea	
  a	
  otro	
  
proceso,	
  el	
  otro	
  proceso	
  leerá	́
  el	
  valor	
  escrito	
  (sin	
  usar	
  
volatile)	
  
Datos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
• Esta	
  forma	
  de	
  hacer	
  que	
  dos	
  hilos	
  puedan	
  acceder	
  a	
  la	
  
información	
  de	
  forma	
  coherente	
  está	
  definida	
  en	
  el	
  Java	
  
Memory	
  Model	
  
http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-­‐133-­‐faq.html	
  
• Siempre	
  hay	
  que	
  usar	
  volatile	
  o	
  sincronizar	
  los	
  hilos	
  para	
  que	
  
no	
  se	
  produzcan	
  problemas	
  de	
  datos	
  obsoletos	
  o	
  eliminado	
  de	
  
variables	
  
18
PROGRAMACIÓN CONCURRENTE EN JAVA
Datos	
  compartidos	
  entre	
  hilos
Objetos	
  compartidos	
  entre	
  hilos
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
• Varios	
  hilos	
  pueden	
  compartir	
  valores	
  de	
  tipos	
  primitivos	
  y	
  
arrays
• Los	
  hilos	
  también	
  pueden	
  compartir	
  objetos	
  de	
  clases	
  de	
  la	
  
librería	
  estándar	
  o	
  desarrolladas	
  por	
  nosotros	
  
• Por	
  ejemplo:	
  Los	
  objetos	
  SimpleSemaphore se	
  comparten	
  
entre	
  hilos	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
• Un	
  objeto	
  se	
  puede	
  compartir	
  entre	
  varios	
  hilos	
  sin	
  problemas	
  
en	
  alguno	
  de	
  los	
  siguientes	
  casos:	
  
• Se	
  accede	
  a	
  los	
  métodos	
  del	
  objeto	
  bajo	
  exclusión	
  mutua	
  (para	
  evitar	
  
que	
  varios	
  hilos	
  ejecuten	
  métodos	
  de	
  forma	
  concurrente	
  y	
  produzcan	
  
intercalaciones	
  no	
  deseadas)	
  
• La	
  clase	
  de	
  ese	
  objeto	
  está	
  preparada	
  para	
  la	
  ejecución	
  concurrente	
  de	
  
sus	
  métodos.	
  
• Los	
  objetos	
  no	
  se	
  modifican	
  durante	
  el	
  tiempo	
  que	
  se	
  comparten	
  entre	
  
los	
  hilos	
  
• La	
  clase	
  es	
  inmutable	
  (sus	
  objetos	
  no	
  cambian	
  nunca	
  de	
  estado)	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
Clases	
  thread-­‐safe
• Una	
  clase	
  es	
  thread-­‐safe si	
  los	
  objetos	
  de	
  la	
  clase	
  se	
  pueden	
  
compartir	
  entre	
  hilos	
  sin	
  necesidad	
  de	
  ponerlos	
  bajos	
  exclusión	
  
mutua	
  
• Todas	
  las	
  clases	
  inmutables	
  (cuyos	
  objetos	
  no	
  cambian	
  de	
  
estado)	
  son	
  thread-­‐safe (p.e:	
  String)	
  
• En	
  la	
  librería	
  existen	
  muchas	
  clases	
  thread-­‐safe (hay	
  que	
  revisar	
  
la	
  documentación)	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
Entonces...	
  ¿Todas	
  las	
  clases	
  que	
  programamos	
  tienen	
  que	
  ser	
  
thread-­‐safe?	
  
• Implementar	
  una	
  clase	
  thread-­‐safe es	
  más	
  difícil	
  que	
  una	
  clase	
  
que	
  no	
  lo	
  es	
  
• Si	
  el	
  objeto	
  finalmente	
  no	
  se	
  comparte	
  entre	
  varios	
  hilos,	
  los	
  
mecanismos	
  que	
  hemos	
  implementado	
  para	
  hacerla	
  thread-­‐
safe posiblemente	
  la	
  hagan	
  menos	
  eficiente	
  
• Lo	
  ideal	
  es	
  hacer	
  que	
  la	
  clase	
  sea	
  inmutable,	
  porque	
  eso	
  la	
  hace	
  
thread-­‐safe y	
  no	
  tiene	
  ninguna	
  penalización	
  en	
  el	
  rendimiento,	
  
pero	
  muchas	
  veces	
  no	
  es	
  posible	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
¿Como	
  sabemos	
  si	
  una	
  clase	
  de	
  la	
  librería	
  es	
  thread-­‐safe	
  o	
  no?	
  
• Si	
  es	
  inmutable,	
  es	
  thread-­‐safe	
  
• En	
  otro	
  caso...	
  hay	
  que	
  revisar	
  la	
  documentación	
  (JavaDoc)	
  o	
  
consultar	
  tutoriales	
  o	
  manuales	
  oficiales	
  
• En	
  algunos	
  casos	
  existen	
  dos	
  versiones	
  de	
  la	
  misma	
  clase,	
  una	
  
para	
  compartir	
  entre	
  hilos	
  y	
  la	
  otra	
  para	
  usarse	
  en	
  un	
  único	
  
hilo	
  (más	
  eficiente)	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
Ejemplo	
  de	
  dos	
  versiones	
  de	
  la	
  misma	
  clase:	
  
• StringBuilder:	
  A	
  mutable	
  sequence	
  of	
  characters.	
  This	
  class	
  
provides	
  an	
  API	
  compatible	
  with	
  StringBuffer,	
  but	
  with	
  no	
  
guarantee	
  of	
  synchronization.	
  This	
  class	
  is	
  designed	
  for	
  use	
  as	
  a	
  
drop-­‐in	
  replacement	
  for	
  StringBuffer in	
  places	
  where	
  the	
  string	
  
buffer	
  was	
  being	
  used	
  by	
  a	
  single	
  thread	
  (as	
  is	
  generally	
  the	
  
case).	
  Where	
  possible,	
  it	
  is	
  recommended	
  that	
  this	
  class	
  be	
  
used	
  in	
  preference	
  to	
  StringBuffer as	
  it	
  will	
  be	
  faster	
  under	
  
most	
  implementations.	
  
• StringBuffer:	
  A	
  thread-­‐safe,	
  mutable	
  sequence	
  of	
  characters.	
  A	
  
string	
  buffer	
  is	
  like	
  a	
  String,	
  but	
  can	
  be	
  modified.	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
¿Mejor	
  usar	
  una	
  clase	
  thread-­‐safe	
  o	
  poner	
  bajo	
  exclusión	
  
mutua	
  objetos	
  de	
  clase	
  no	
  thread	
  safe?	
  
• Siempre	
  que	
  exista	
  una	
  clase	
  thread-­‐safe	
  es	
  mejor	
  usarla	
  
• Poner	
  un	
  objeto	
  bajo	
  exclusión	
  mutua	
  puede	
  reducir	
  el	
  
paralelismo	
  innecesariamente	
  
• Los	
  implementadores	
  de	
  una	
  clase	
  thread-­‐safe	
  intentan	
  que	
  
sea	
  lo	
  más	
  paralela	
  posible,	
  es	
  decir,	
  reducen	
  al	
  mínimo	
  las	
  
zonas	
  que	
  no	
  pueden	
  ejecutarse	
  de	
  forma	
  concurrente	
  
Objetos	
  compartidos	
  entre	
  hilos
PROGRAMACIÓN CONCURRENTE EN JAVA
¿Cómo	
  se	
  implementa	
  una	
  clase	
  thread-­‐safe?	
  
• Basta	
  con	
  asegurarse	
  de	
  que	
  sus	
  métodos	
  se	
  pueden	
  ejecutar	
  
por	
  varios	
  hilos	
  de	
  forma	
  concurrente	
  sin	
  condiciones	
  de	
  
carrera	
  ni	
  intercalaciones	
  no	
  deseadas	
  
• Cómo
• Haciéndola	
  inmutable o…
• Sincronizando	
  los	
  hilos	
  de	
  alguna	
  forma...	
  (exclusión	
  mutua,	
  
sincronización...)	
  o...	
  
• Con	
  atributos	
  thread-­‐safe...	
  
Objetos	
  compartidos	
  entre	
  hilos
EJERCICIO
PROGRAMACIÓN CONCURRENTE
EN JAVA
Crear	
  una	
  clase	
  thread-­‐safe	
  llamada	
  SincCond	
  que	
  implemente	
  
una	
  sincronización	
  condicional	
  entre	
  dos	
  hilos	
  
•Métodos:	
  
• await:	
  El	
  hilo	
  que	
  ejecute	
  ese	
  método	
  no	
  podrá	́
  continuar	
  hasta	
  que	
  otro	
  hilo	
  
ejecute	
  signal	
  
• signal:	
  Cuando	
  se	
  ejecute	
  este	
  método,	
  el	
  hilo	
  que	
  ejecuta	
  await	
  podrá	́
  continuar	
  
• La	
  clase	
  tendrá	́
  los	
  atributos	
  necesarios	
  para	
  implementar	
  estos	
  
métodos	
  con	
  espera	
  activa	
  
Objetos	
  compartidos	
  entre	
  hilos
EJERCICIO	
  2
PROGRAMACIÓN CONCURRENTE EN JAVA
• Refactorizar el	
  
programa	
  
ProdCons para	
  
usar	
  la	
  clase	
  
SincCond
22
Ejercicio 1
• Refactorizar el 
programa 
ProdCons para 
usar la clase 
SincCond
CONCURRENCIA Y ORIENTACIÓN A OBJETOS
public class ProdCons {
static volatile boolean producido = false;
static volatile double producto;
public static void productor() {
producto = Math.random();
producido = true;
}
public static void consumidor() {
while (!producido);
print("Producto: "+producto);
}
public void exec() {
new Thread(()-> productor()).start();
new Thread(()-> consumidor()).start();
 }
 public static void main(String[] args){
 new ProdCons().exec();
 } 
}
Objetos	
  compartidos	
  entre	
  hilos
EJERCICIO	
  2
PROGRAMACIÓN CONCURRENTE EN JAVA
•Mejorar	
  la	
  clase	
  SincCond para	
  que	
  permita	
  la	
  creación	
  
de	
  productos	
  indefinidos
• Actualizar	
  la	
  clase	
  ProdCons para	
  que	
  se	
  produzcan	
  y	
  
consuman	
  10	
  productos
__MACOSX/Academia/Tema5/._Tema 5.4 - Concurrencia y orientación a objetos UPDATED.pdf
Academia/Tema5/Tema 5.6 - Estructuras de datos concurrentes.pdf
Programación
Concurrente
Tema 5
Programación 
Concurrente en 
Java
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
Programación
Concurrente
Tema 5.5
Estructuras 
de datos 
concurrentes
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
3
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
4
Introducción
• Como ocurre con cualquier otro objeto, 
compartir estructuras de datos entre varios 
hilos es muy delicado 
• Hay que elegir algunas de las siguientes 
alternativas para evitar condiciones de 
carrera:
 Poner bajo exclusión mutua el acceso a las 
estructuras compartidas
 Usar estructuras de datos thread-safe, que estén 
preparadas para ser usadas desde varios hilos
ESTRUCTURAS DE DATOS CONCURRENTES
5
Introducción
• En la API de Java existe un conjunto de clases e 
interfaces para la gestión de estructuras de 
datos llamado Java Collections Framework
• Este framework permite usar varios tipos de 
estructuras de datos (listas, mapas, 
conjuntos, colas…)
• Existen versiones optimizadas para ser usadas 
por un único hilo y otras versiones diseñadas 
para ser compartidas entre hilos
ESTRUCTURAS DE DATOS CONCURRENTES
6
Introducción
• Al igual que existen estructuras de datos 
preparadas para ser compartidas entre hilos, la 
API de Java también ofrece clases para 
compartir valores atómicos entre hilos 
(thread-safe).
• Con estas clases se pueden compartir valores 
de tipo entero y objetos
• Disponen de métodos que realizan 
operaciones básicas bajo exclusión mutua
ESTRUCTURAS DE DATOS CONCURRENTES
7
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
8
Valores atómicos concurrentes
• El paquete java.util.concurrent.atomic tiene varias 
clases que permiten compartir un valor atómico 
entre varios hilos (Ver documentación del paquete).
• Dispone de clases para:
 Tipos primitivos: Integer, Long, Boolean
 Objetos
 Arrays de tipos primitivos y objetos
 Otras clases más avanzadas
ESTRUCTURAS DE DATOS CONCURRENTES
http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html 
http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
9
AtomicInteger
• Se usa para compartir una variable de tipo entero entre 
varios hilos. 
• Habitualmente usada para tener un contador
•Métodos: 
 int get(): Devuelve el valor del entero
 int getAndIncrement(): Devuelve e incrementa
 int getAndSet(int newValue): Devuelve y establece un nuevo 
valor
 boolean compareAndSet(int expect, int update): Establece el 
nuevo valor si el anterior era el esperado.
VALORES ATÓMICOS CONCURRENTES
10
AtomicInteger
• Ejemplo de uso
VALORES ATÓMICOS CONCURRENTES
class AtomicCounter {
 private AtomicInteger c 
 = new AtomicInteger(0);
 public void increment() {
 c.incrementAndGet();
 }
 public void decrement() {
 c.decrementAndGet();
 }
 public int value() {
 return c.get();
 }
}
class SynchronizedCounter {
 private int c = 0; 
 public synchronized void inc(){ 
 c++; 
 } 
 public synchronized void dec(){ 
 c--; 
 }
 public synchronized int get(){ 
 return c;
 } 
}
11
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
12
Estructuras de datos en Java
• El Java Collections Framework es un conjunto 
de clases e interfaces de la librería estándar 
para gestionar colecciones de elementos. 
• Permite gestionar listas, conjuntos, colas y 
mapas
ESTRUCTURAS DE DATOS CONCURRENTES
http://docs.oracle.com/javase/8/docs/technotes/guides/collections/ 
http://docs.oracle.com/javase/8/docs/technotes/guides/collections/
13
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
14
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
15
Genéricos
• Los genéricos son un mecanismo utilizado en 
los lenguajes de programación con tipado 
estático para especificar el tipo de los 
elementos de una estructura de datos
• En C++ se les denomina plantillas (templates)
• Aparte de las estructuras de datos, también 
puede utilizarse en otros contextos
ESTRUCTURAS DE DATOS EN JAVA
16
Genéricos
• Clase pila de 
fracciones 
implementada 
con un array
ESTRUCTURAS DE DATOS EN JAVA
class PilaFracciones {
 private Fraccion[] elementos;
 private int numElementos;
 public PilaFracciones(int tope) {
 this.elementos = new Fraccion[tope];
 this.setNumElementos(0);
 }
 
 public Fraccion getElemento(int indice) {
 return this.elementos[indice];
 }
 public void addElemento(Fraccion fraccion) {
 if(numElementos < elementos.length){
 this.elementos[numElementos] = fraccion;
 numElementos++;
 }
 }
 ... 
}
17
Genéricos
• Uso de la 
clase pila de 
fracciones
ESTRUCTURAS DE DATOS EN JAVA
public static void main(String[] args) {
 PilaFracciones pila = new PilaFracciones(5);
 
 Fraccion fraccion1 = new Fraccion(1,2);
 Fraccion fraccion2 = new Fraccion(1,3);
 Fraccion fraccion3
= new Fraccion(1,4);
 
 pila.addElemento(fraccion1);
 pila.addElemento(fraccion2);
 pila.addElemento(fraccion3);
 ... 
}
18
Genéricos
• ¿Si queremos 
implementar 
pila de 
Intervalos?
• ¿Creamos otra 
clase 
cambiando 
Fracción por 
Intervalo?
• Duplicar código 
es malo
ESTRUCTURAS DE DATOS EN JAVA
class PilaIntervalos {
 private Intervalo[] elementos;
 private int numElementos;
 public PilaIntervalos(int tope) {
 this.elementos = new Intervalo[tope];
 this.setNumElementos(0);
 }
 
 public Intervalo getElemento(int indice) {
 return this.elementos[indice];
 }
 public void addElemento(Intervalo intervalo) {
 if(numElementos < elementos.length){
 this.elementos[numElementos] = intervalo;
 numElementos++;
 }
 } 
 ... 
}
19
Genéricos
• Uso de la 
clase pila de 
intervalos
ESTRUCTURAS DE DATOS EN JAVA
public static void main(String[] args) {
 PilaIntervalos pila = new PilaIntervalos(5);
 
 Intervalo intervalo1 = new Intervalo(1,2);
 Intervalo intervalo2 = new Intervalo(1,3);
 Intervalo intervalo3 = new Intervalo(1,4);
 
 pila.addElemento(intervalo1);
 pila.addElemento(intervalo2);
 pila.addElemento(intervalo3);
 ... 
}
20
Genéricos
• ¿Si queremos 
implementar 
pila de 
Intervalos?
• Podemos usar 
el 
polimorfismo
• ¿Hacemos que 
los elementos 
sean de tipo 
Object?
ESTRUCTURAS DE DATOS EN JAVA
class PilaObjects {
 private Object[] elementos;
 private int numElementos;
 public PilaObjects(int tope) {
 this.elementos = new Object[tope];
 this.setNumElementos(0);
 }
 
 public Object getElemento(int indice) {
 return this.elementos[indice];
 }
 public void addElemento(Object object) {
 if(numElementos < elementos.length){
 this.elementos[numElementos] = object;
 numElementos++;
 }
 } 
 ... 
}
21
Genéricos
• Usar Object funciona 
• Pero el compilador no nos ayuda
• Se puede escribir código incorrecto
ESTRUCTURAS DE DATOS EN JAVA
//Queremos que la pila contenga Intervalos
PilaObjects pilaIntervalos = new PilaObjects(5);
pilaIntervalos.addElemento(new Intervalo(2,4));
pilaIntervalos.addElemento(new Intervalo(2,6));
…
//Nos equivocamos y el compilador no da error
//No se genera una excepción en ejecución
pilaIntervalos.addElemento(new Fraccion(1,2));
…
//Siempre que sacamos intervalos tenemos
//que hacer casting. Puede generar una excepción 
Intervalo intervalo = (Intervalo) pilaIntervalos.getElemento()
22
Genéricos
• Lo ideal sería definir el tipo de los elementos 
cuando se usa la pila, no cuando se 
implementa la pila
ESTRUCTURAS DE DATOS EN JAVA
public static void main(String[] args) {
 PilaIntervalo pilaIntervalo;
 PilaFraccion pilaFraccion;
 
 ... 
}
23
Genéricos
• Un tipo genérico es un tipo usado al 
implementar una clase que se concretará 
cuando se use la clase
 Al declarar una variable, parámetro o atributo
 Al instanciar un objeto
ESTRUCTURAS DE DATOS EN JAVA
24
Genéricos
ESTRUCTURAS DE DATOS EN JAVA
class Pila<E> {
 public Pila(int tope) { ... }
 
 public E getElemento(int indice) { ... }
 public void addElemento(E elem) { ... }
 ... 
}
class PilaIntervalos {
 public PilaIntervalos(int tope) { ... }
 
 public Intervalo getElemento(int indice) { ... }
 public void addElemento(Intervalo intervalo) { ... } 
}
25
Genéricos
ESTRUCTURAS DE DATOS EN JAVA
Sin usar 
genéricos
//Queremos que la pila contenga Intervalos
PilaIntervalos pilaIntervalos = new PilaIntervalos(5);
Intervalo i1 = new Intervalo(2,4);
Intervalo i2 = new Intervalo(2,6);
pilaIntervalos.addElemento(i1);
pilaIntervalos.addElemento(i2);
…
Intervalo i3 = pilaIntervalos.getElemento(1);
26
Genéricos
ESTRUCTURAS DE DATOS EN JAVA
Sin usar 
genéricos
Usando 
genéricos
//Queremos que la pila contenga Intervalos
PilaIntervalos pilaIntervalos = new PilaIntervalos(5);
Intervalo i1 = new Intervalo(2,4);
Intervalo i2 = new Intervalo(2,6);
pilaIntervalos.addElemento(i1);
pilaIntervalos.addElemento(i2);
…
Intervalo i3 = pilaIntervalos.getElemento(1);
//Queremos que la pila contenga Intervalos
Pila<Intervalo> pilaIntervalos = new Pila<>(5);
Intervalo i1 = new Intervalo(2,4);
Intervalo i2 = new Intervalo(2,6);
pilaIntervalos.addElemento(i1);
pilaIntervalos.addElemento(i2);
…
Intervalo i3 = pilaIntervalos.getElemento(1)
27
Genéricos
ESTRUCTURAS DE DATOS EN JAVA
Sin usar 
genéricos
Usando 
genéricos
Pila<Intervalo> pilaIntervalos = new Pila<>(5);
Pila<Fraccion> pilaFracciones = new Pila<>(5);
…
Intervalo intervalo = pilaIntervalos.getElemento(1);
Fraccion fraccion = pilaFracciones.getElemento(1);
PilaIntervalos pilaIntervalos = new PilaIntervalos(5);
PilaFracciones pilaFracciones = new PilaFracciones(5);
…
Intervalo intervalo = pilaIntervalos.getElemento(1); 
Fraccion fraccion = pilaFracciones.getElemento(1);
28
Genéricos
• ¿Cómo se implementaría un método que admitiera 
pilas, independientemente de cual sea el tipo de sus 
elementos?
• Al símbolo ? Se le denomina comodín (wildcard)
• Es importante conocer este símbolo porque se utiliza 
en diversos métodos del Java Collections Framework
ESTRUCTURAS DE DATOS EN JAVA
public void sacarElementos(Pila<?> pila, int numElems){
 for(int i=0; i<numElementos; i++){
 pila.pop();
 }
}
29
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
30
Listas, conjuntos y mapas
• A las estructuras de datos en Java se las 
denomina colecciones en vez de estructuras 
de datos
• Con este nombre se enfatiza que son objetos 
que mantienen una colección de elementos, 
independientemente de su estructura interna
• Esto permite separar la parte pública (interfaz) 
de los detalles de implementación internos
ESTRUCTURAS DE DATOS EN JAVA
31
Listas, conjuntos y mapas
• Interfaces
 Permiten manipular las colecciones independientemente de la 
implementación particular. 
 Definen la funcionalidad, no cómo debe implementarse esa 
funcionalidad
• Implementación
 Clases que implementan los interfaces que definen los tipos de 
colecciones (listas, mapas y conjuntos)
• Algoritmos
 Métodos que realizan computaciones sobre colecciones de 
elementos
 Búsqueda, ordenación, etc. 
ESTRUCTURAS DE DATOS EN JAVA
32
Listas, conjuntos y mapas
• Tipos de colecciones
ESTRUCTURAS DE DATOS EN JAVA
33
Listas, conjuntos y mapas
• Set<E>
 Colección que no mantiene el orden
de inserción y que no puede tener 
dos o más objetos iguales
• List<E>
 Colección que sí mantiene
el orden de inserción y que 
puede contener elementos duplicados
• Queue<E>
 Colección para almacenar múltiples 
elementos antes de ser procesados. 
Especialmente utilizada en programas 
concurrentes
ESTRUCTURAS DE DATOS EN JAVA
34
Listas, conjuntos y mapas
•Map<K,V>
 Estructura que guarda los 
elementos (valores) 
asociados a una clave
ESTRUCTURAS DE DATOS EN JAVA
35
Listas, conjuntos y mapas
• Collection<E>
 Colección genérica
 Se pueden consultar el 
número de elementos
 Se pueden añadir y 
eliminar elementos:
 Hay colecciones que permiten elementos duplicados y otras no
 Hay colecciones ordenadas o desordenadas
 Hay colecciones que permiten el valor null, otras no
ESTRUCTURAS DE DATOS EN JAVA
36
Listas, conjuntos y mapas
ESTRUCTURAS DE DATOS EN JAVA
• Algunos métodos de Collection<E>
 Para agregar y eliminar elementos
 boolean add(E e)
 boolean remove(Object o)
 Para realizar consultas
 int size()
 boolean isEmpty()
 boolean contains(Object o)
 Para realizar varias operaciones de forma simultánea
 boolean containsAll(Collection<?>
collection)
 void clear()
 boolean removeAll(Collection<?> collection)
37
Listas, conjuntos y mapas
• Iterable<T>
 Permite acceder a los 
elementos uno por uno
 No permite consultar 
el número de elementos
 No permite añadir o eliminar elementos
 Se utiliza para recorrer una colección
ESTRUCTURAS DE DATOS EN JAVA
38
Listas, conjuntos y mapas
• Estos interfaces tienen una implementación 
por defecto que puede ser utilizada en la 
mayoría de los casos
 List<E>: ArrayList<E>
 Set<E>: HashSet<E>
 Map<K,V>: HashMap<K,V>
 Queue<E>: 
 LinkedList<E>: Cola FIFO
 PriorityQueue<E>: Ordena sus elementos por prioridad
ESTRUCTURAS DE DATOS EN JAVA
39
Listas, conjuntos y mapas
ESTRUCTURAS DE DATOS EN JAVA
40
Listas (List<E>)
• Interfaces vs Implementaciones
 Las variables, parámetros y atributos se declaran 
con el tipo de las interfaces 
 La clase de implementación sólo se usa para 
instanciar los objetos
 Se abstrae lo más posible de la implementación 
concreta (y se puede cambiar fácilmente en el 
futuro)
ESTRUCTURAS DE DATOS EN JAVA
List<String> nombres = new ArrayList<>();
41
Listas (List<E>)
• Colección que mantiene el orden de inserción y que 
puede contener elementos duplicados
• Similar a un array pero que crece de forma dinámica
• Se accede a los elementos indicando su posición
• Algunos métodos:
 void add(int index, E element)
 boolean addAll(int index, Collection<? extends E> c)
 E get(int index)
 E remove(int index)
ESTRUCTURAS DE DATOS EN JAVA
42
Listas (List<E>)
• Es la estructura de datos más usada
• Es la estructura de datos más eficiente para la 
inserción de elementos (al final)
• No obstante, no es muy eficiente para 
búsquedas (porque son secuenciales)
ESTRUCTURAS DE DATOS EN JAVA
43
Listas (List<E>)
• ArrayList<E> es la clase por defecto que 
implementa List<E>
ESTRUCTURAS DE DATOS EN JAVA
//Declaro la variable del tipo de la interfaz,
//y le asigno un objeto del tipo de la clase de
//implementación.
List<Intervalo> listaIntervalos = new ArrayList<>();
List<Fraccion> listaFracciones = new ArrayList<>();
listaIntervalos.add(new Intervalo(2,4));
listaFracciones.add(new Fraccion(2,6));
…
Intervalo intervalo = listaIntervalos.get(0);
Fraccion fraccion = listaFracciones.get(0);
44
Ejercicio 1
• Crear un ejemplo básico para probar el 
funcionamiento de las listas
 Declarar una lista de String. 
 Añadir y eliminar elementos de la lista
 Definir un método addElemToList(…) que reciba 
una lista de String y un String como parámetro y 
añada el String a la lista
ESTRUCTURAS DE DATOS EN JAVA
45
Conjuntos (Set<E>)
• No mantiene el orden de inserción
• No es posible recuperar los elementos en el 
orden en que fueron insertados
• No admite elementos duplicados
• Si se añade un objeto al conjunto y ya había 
otro igual, no se produce ningún cambio en el 
conjunto
ESTRUCTURAS DE DATOS EN JAVA
46
Conjuntos (Set<E>)
• Es la estructura de datos más eficiente 
buscando elementos
• Pero eso hace que la inserción sea más 
costosa que en las listas
ESTRUCTURAS DE DATOS EN JAVA
47
Conjuntos (Set<E>)
• HashSet<E> es la implementación por defecto 
de Set<E> y se implementa utilizando una 
tabla hash
ESTRUCTURAS DE DATOS EN JAVA
//Declaro la variable del tipo de la interfaz,
//y le asigno un objeto del tipo de la clase de
//implementación.
Set<Intervalo> intervalos = new HashSet<>();
Intervalo intervalo = new Intervalo(2,4);
intervalos.add(intervalo);
//Esta inserción no tiene efecto
intervalos.add(intervalo);
int numIntervalos = intervalos.size(); // Devuelve 1
48
Mapas (Map<K,V)
• Define una estructura de datos que asocia 
(mapea) claves con valores
• No permite claves repetidas
• Varias claves distintas pueden estar asociadas 
al mismo valor (valores repetidos)
• La búsqueda de un valor asociado a una clave 
es muy eficiente
ESTRUCTURAS DE DATOS EN JAVA
49
Mapas (Map<K,V)
•Métodos más importantes:
 V put(K key, V value): Insertar un valor asociado a 
la clave
 V get(Object key): Obtener un valor asociado a la 
clave
 Collection<V> values(): Devuelve la colección de 
valores
 Set<K> keySet(): Devuelve el conjunto de claves
 Entry<K,V> entrySet(): Devuelve el conjunto de 
pares clave-valor (entradas del mapa)
ESTRUCTURAS DE DATOS EN JAVA
50
Mapas (Map<K,V)
• HashMap<K,V> es la implementación por 
defecto de Map<K,V> que implementa el 
conjunto de datos utilizando una tabla hash
ESTRUCTURAS DE DATOS EN JAVA
Map<String, Coche> propietarios = new HashMap<>(5);
Coche toledo = new Coche(“Seat”, “Toledo”, 110)
Coche punto = new Coche(“Fiat”, ”Punto”, 90);
propietarios.put(“M-1233-YYY”, toledo);
propietarios.put(“M-1234-ZZZ”, punto);
Coche c = propietarios.get(“M-1234-ZZZ”);
51
Mapas (Map<K,V)
ESTRUCTURAS DE DATOS EN JAVA
Map<String, String> configuracion = new HashMap<>();
configuracion.put(“lenguaje”, “ingles”);
configuracion.put(“servidor”, ”http://...”);
Configuracion.put(“correo”, “a.b@xyz”);
…
String lenguaje = configuracion.get(“lenguaje”);
String servidor = configuracion.get(“servidor”);
52
Ejercicio 2
• Se tiene una colección de aeropuertos (objetos de la 
clase Aeropuerto), y se desea poder obtener un 
aeropuerto dado su nombre
• Declarar la estructura de datos adecuada para asociar el 
nombre de cada aeropuerto con el objeto aeropuerto 
correspondiente
• Introducir varios aeropuertos asociados a sus nombres: 
“El Prat”, “Barajas”, “Castellón”
• Obtener el objeto aeropuerto dado su nombre: 
“Barajas”
ESTRUCTURAS DE DATOS EN JAVA
53
Estructuras de datos ordenadas
• Cuando los elementos son comparables entre 
sí, puede ser útil insertar de forma ordenada 
los elementos
• SortedSet<E>: Ordena los elementos
 Implementación TreeSet<E>
• SortedMap<E>: Ordena las claves
 Implementación TreeMap<E>
ESTRUCTURAS DE DATOS EN JAVA
54
Otras implementaciones
• Aparte de las implementaciones por defecto, 
existen otras implementaciones para situaciones 
especiales
 De propósito general
 De propósito específico
 Para soporte de concurrencia
 Combinadas
 Optimizadas para el acceso secuencial
 Optimizadas para el acceso aleatorio
ESTRUCTURAS DE DATOS EN JAVA
55
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
56
Recorrer una colección
• Acceder a cada elemento de una colección 
depende de su tipo:
 Lista
 Acceso por posición con bucle for
 Acceso secuencial
 Conjunto
 Acceso secuencial
 Mapa
 Acceso secuencial a la colección de valores
 Acceso secuencial al conjunto de claves
 Acceso secuencial al conjunto de entradas
ESTRUCTURAS DE DATOS EN JAVA
57
Recorrer una Lista
• Acceso por posición con bucle for
• No se recomienda, sobre todo en estructuras de datos basadas 
en listas enlazadas (LinkedList<E>) puede ser ineficiente frente 
al acceso secuencial
RECORRER UNA COLECCIÓN
List<String> ciudades = new ArrayList<>();
ciudades.add("Ciudad Real");
ciudades.add("Madrid");
ciudades.add("Valencia");
for (int i=0; i < ciudades.size(); i++) {
 String ciudad = ciudades.get(i);
 System.out.println(ciudad + "\n");
}
58
Recorrer una Lista
• Acceso secuencial
 Se puede realizar con el for mejorado
 Se puede realizar con el iterador
RECORRER UNA COLECCIÓN
59
Recorrer una Lista
• Acceso secuencial con for mejorado
• En general es la forma preferida
RECORRER UNA COLECCIÓN
List<String> ciudades = new ArrayList<>();
ciudades.add("Ciudad Real");
ciudades.add("Madrid");
ciudades.add("Valencia");
for (String ciudad: ciudades) {
 System.out.println(ciudad + "\n");
}
60
Recorrer una
Lista
• Acceso secuencial con iterador
RECORRER UNA COLECCIÓN
interface Iterator<E> {
 boolean hasNext();
 E next();
 void remove();
}
List<String> ciudades = new ArrayList<>();
ciudades.add("Ciudad Real");
ciudades.add("Madrid");
ciudades.add("Valencia");
Iterator<String> it = ciudades.iterator();
while (it.hasNext()){
 String s = it.next();
 System.out.println(s + "\n");
}
61
Recorrer una Lista
• Acceso secuencial con iterador
 Es la única forma de borrar elementos de una lista 
mientras se recorre
RECORRER UNA COLECCIÓN
List<String> ciudades = new ArrayList<>();
ciudades.add("Ciudad Real");
ciudades.add("Madrid");
ciudades.add("Valencia");
Iterator<String> it = ciudades.iterator();
while (it.hasNext()){
 String ciudad = it.next();
 if(ciudad.equals(“Madrid”)){
 it.remove(); 
 }
}
62
Recorrer un Conjunto
• Sólo se pueden recorrer los elementos de un 
conjunto con acceso secuencial
• Con for mejorado o con iteradores
RECORRER UNA COLECCIÓN
Set<String> ciudades = new HashSet<>();
ciudades.add("Ciudad Real");
ciudades.add("Madrid");
ciudades.add("Valencia");
for (String ciudad: ciudades) {
 System.out.println(ciudad + "\n");
}
63
Recorrer un Mapa
• Formas de recorrer un mapa
 Acceso secuencial a la colección de valores
RECORRER UNA COLECCIÓN
Map<String, Coche> propietarios = new HashMap<>(5);
coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110));
coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90));
for (Coche coche : coches.values()) {
 System.out.println(“Coche: “+coche);
}
64
Recorrer un Mapa
• Formas de recorrer un mapa
 Acceso secuencial al conjunto de claves
RECORRER UNA COLECCIÓN
Map<String, Coche> propietarios = new HashMap<>(5);
coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110));
coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90));
for (String matricula : coches.keySet()) {
 System.out.println(“Matricular: “+matricula);
}
65
Recorrer un Mapa
• Formas de recorrer un mapa
 Acceso secuencial al conjunto de entradas
RECORRER UNA COLECCIÓN
Map<String, Coche> propietarios = new HashMap<>(5);
coches.put(“M-1233-YYY”, new Coche(“Seat”, “Toledo”, 110));
coches.put(“M-1234-ZZZ”, new Coche(“Fiat”, ”Punto”, 90));
for (Entry<String,Coche> e : coches.entrySet()) {
 System.out.println(“Mat:”+e.getKey()+“ “+e.getValue());
}
66
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
67
Ordenación y Búsqueda
• Sólo se pueden ordenar las listas
• Ordenación de elementos con métodos estáticos en 
la clase Collections
ESTRUCTURAS DE DATOS EN JAVA
List<String> nombres = new ArrayList<>();
nombres.add("Pepe");
nombres.add("Juan");
nombres.add("Antonio");
Collections.sort(nombres);
System.out.println(nombres);
[Antonio, Juan, Pepe]
68
Ordenación y Búsqueda
• Se puede especificar el orden de comparación
 Se usa una lambda que devuelve un valor positivo si 
o1 es mayor que 02. Negativo en caso contrario 
ESTRUCTURAS DE DATOS EN JAVA
List<String> nombres = new ArrayList<>();
nombres.add("Juanin");
nombres.add("Pepe");
nombres.add("Antonio");
Collections.sort(nombres,(o1,o2)-> o1.length()-o2.length());
[Pepe, Juanin, Antonio]
69
Ordenación y Búsqueda
• Se puede especificar el orden de comparación
 Se puede usar Comparator.comparing(…) 
ESTRUCTURAS DE DATOS EN JAVA
List<String> nombres = new ArrayList<>();
nombres.add("Juanin");
nombres.add("Pepe");
nombres.add("Antonio");
Collections.sort(nombres, 
 Comparator.comparing(String::length));
[Pepe, Juanin, Antonio]
70
Ordenación y Búsqueda
• ¿Qué es una búsqueda?
 En un conjunto es saber si está el elemento
 En un mapa es obtener el valor asociado a la 
clave (si está)
 En una lista es saber su posición (si está)
ESTRUCTURAS DE DATOS EN JAVA
71
Ordenación y Búsqueda
•Búsqueda en conjuntos
 Método: boolean contains(Object o)
•Búsqueda en mapas
 Método : V get(Object key)
ESTRUCTURAS DE DATOS EN JAVA
72
Ordenación y Búsqueda
• Búsquedas en listas
 Si la lista está desordenada, usamos el método 
int indexOf(E e)
 Si la lista está ordenada, se puede usar una 
búsqueda binaria (método en la clase Collections)
 Si el elemento está en la lista, devuelve su posición
 Si el elemento no está en la lista, devuelve el lugar en 
el que debería estar
ESTRUCTURAS DE DATOS EN JAVA
73
Ordenación y Búsqueda
ESTRUCTURAS DE DATOS EN JAVA
List<String> nombres = new ArrayList<>();
nombres.add("Pepe");
nombres.add("Juan");
nombres.add("Antonio");
Collections.sort(nombres);
//int pos = nombres.indexOf("Mario");
int pos = Collections.binarySearch(nombres, "Mario");
if (pos < 0){
 //El nombre no está en la lista
 int insertPos = -pos-1; 
 System.out.println("No está. Debería estar en: "+insertPos);
} else {
 System.out.println("Está en la posición: "+pos);
}
74
Ordenación y Búsqueda
• Hay que elegir muy bien la estructura de datos que se utiliza 
en un programa
 Listas (basadas en arrays)
 Eficiente la inserción al final O(1)
 Eficiente el acceso por posición O(1)
 Ineficiente la búsqueda O(n)
 Conjuntos (basados en hash)
 Eficiente la inserción O(1) (aunque menos que la lista)
 No se puede hacer acceso por posición
 Eficiente la búsqueda O(1)
 Mapas (basados en hash)
 Igual que los conjuntos
ESTRUCTURAS DE DATOS EN JAVA
75
Ejercicio 3
• Implementar una aplicación que permita gestionar en memoria un conjunto 
de viajes de una aerolínea
• Cada viaje se representa con la ciudad origen, destino y la duración del viaje 
(clase Viaje)
• Se dan de alta los viajes en un gestor (clase GestorViajes)
• Al gestor de viajes se le pueden pedir:
 Devolver todos los viajes que tienen una determinada ciudad origen
 Devolver todos los viajes que tienen una determinada ciudad destino
 Devolver todos los viajes 
 Devolver todas las ciudades en las que hay viajes
• Hay que conseguir el menor tiempo de ejecución de las consultas, aunque 
sean necesarias varias estructuras de datos
ESTRUCTURAS DE DATOS EN JAVA
76
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
77
Equals y hashcode
• ¿Objetos iguales o el mismo objeto?
 Dos variables apuntan al mismo objeto si al 
comparar con == se obtiene verdadero (true)
 Dos variables apuntan a objetos iguales si al 
comparar con el método equals se obtiene 
verdadero (true)
ESTRUCTURAS DE DATOS EN JAVA
78
Equals y hashcode
ESTRUCTURAS DE DATOS EN JAVA
“a” “a” “b”v1
v3
v4
Expresión Resultado
v1 == v2 true
v1.equals(v2) true
v1 == v3 false
v1.equals(v3) true
v1 == v4 false
v1.equals(v4) false
v2
79
Equals y hashcode
• Estructuras de datos basadas en hash
 La estructura de datos HashSet utiliza los métodos 
equals y hashcode de los objetos que se insertan en 
ella
 La estructura de datos HashMap utiliza los 
métodos equals y hashcode de las claves que se 
asocian a valores
 Los métodos equals y hashcode se usan para saber 
si dos objetos son iguales o diferentes
ESTRUCTURAS DE DATOS EN JAVA
80
Equals y hashcode
• Estructuras de datos basadas en hash
 Las clases String, Date, Double… implementan 
correctamente los métodos equals y hashcode 
para que funcionen correctamente con HashSet y 
como claves de un HashMap
 Las clases implementadas por el desarrollador no 
tienen esta funcionalidad por defecto 
ESTRUCTURAS DE DATOS EN JAVA
81
Equals y hashcode
• Estructuras de datos basadas en hash
 Si
en una clase propia no implementamos los 
métodos equals y hashcode
 Nunca existirán dos objetos iguales de esa clase 
(aunque tengan los mismos valores de los atributos)
 Si se intenta insertar el mismo objeto en un conjunto 
por segunda vez, no tendrá efecto. 
 Si se intenta insertar un objeto con los mismos valores 
de atributos que otro ya incluido, se insertará el nuevo 
valor (porque a ojos del HashSet, no son iguales)
ESTRUCTURAS DE DATOS EN JAVA
82
Equals y hashcode
• Estructuras de datos basadas en hash
 Si en una clase propia si implementamos los 
métodos equals y hashcode
 Decidimos cuándo dos objetos de esa clase se 
consideran iguales (habitualmente si algunos de sus 
atributos son iguales)
 Si se intenta insertar el mismo objeto en un conjunto 
por segunda vez, no tendrá efecto. 
 Si se intenta insertar un objeto igual que otro ya 
incluido, no tendrá efecto.
ESTRUCTURAS DE DATOS EN JAVA
83
Equals y hashcode
• ¿Cómo se implementan los métodos equals y 
hashcode?
 Se deciden qué atributos tienen que ser iguales para 
que los objetos se consideren iguales (pueden ser 
todos).
 El entorno de desarrollo genera automáticamente 
el código en base a esos atributos
ESTRUCTURAS DE DATOS EN JAVA
* Item 9 en "Effective Java Programming Language Guide,
second edition”, (Addison-Wesley,2008) de Joshua Bloch
84
Equals y 
hashcode
public class Fraccion {
 private float numerador;
 private float denominador;
 @Override
 public int hashCode() {
 final int prime = 31;
 int result = 1;
 result = prime * result + Float.floatToIntBits(denominador);
 result = prime * result + Float.floatToIntBits(numerador);
 return result;
 }
 @Override
 public boolean equals(Object obj) {
 if (this == obj)
 return true;
 if (obj == null)
 return false;
 if (getClass() != obj.getClass())
 return false;
 Fraccion other = (Fraccion) obj;
 if (Float.floatToIntBits(denominador) != Float
 .floatToIntBits(other.denominador))
 return false;
 if (Float.floatToIntBits(numerador) != Float
 .floatToIntBits(other.numerador))
 return false;
 return true;
 }
}
85
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
86
Colecciones con tipos primitivos
• Existen clases que se comportan como los 
tipos primitivos (clases de envoltura, wrapper)
 Integer, Double, Float, Boolean, Character...
• El autoboxing y autounboxing es la capacidad 
de conversión automática entre un valor de un 
tipo primitivo y un objeto de la clase 
correspondiente
ESTRUCTURAS DE DATOS EN JAVA
int numero = 3;
Integer numObj = numero;
int otroNum = numObj;
87
Colecciones con tipos primitivos
• Esto permite usar las colecciones con tipos 
primitivos
• Hay que ser consciente de que se tienen que 
realizar conversiones y eso es costoso
ESTRUCTURAS DE DATOS EN JAVA
List<Integer> enteros = new ArrayList<>();
enteros.add(3);
enteros.add(5);
enteros.add(10);
int num = enteros.get(0);
88
Colecciones con tipos primitivos
• Existen implementaciones de terceros con 
estructuras de datos especialmente diseñadas 
para tipos primitivos
• Deben usarse cuando se utilizan mucho en un 
programa y las conversiones sean muy 
numerosas
 http://trove4j.sourceforge.net/ 
 http://fastutil.dsi.unimi.it/ 
 http://commons.apache.org/primitives/ 
ESTRUCTURAS DE DATOS EN JAVA
http://trove4j.sourceforge.net/
http://fastutil.dsi.unimi.it/
http://commons.apache.org/primitives/
89
Estructuras de datos en Java
• Genéricos
• Listas, conjuntos y mapas
• Recorrer una colección
• Ordenación y Búsqueda
• Equals y hashcode
• Colecciones con tipos primitivos
• Ventajas de la colecciones
ESTRUCTURAS DE DATOS CONCURRENTES
90
Ventajas de las colecciones
• Reducción del esfuerzo del programador
• Incremento de la velocidad y calidad
• Interoperabilidad entre APIs no relacionadas
•Menor esfuerzo de aprendizaje y uso de otras 
APIs
• Fomenta la reutilización del software
ESTRUCTURAS DE DATOS EN JAVA
91
Ventajas de las colecciones
• Aunque las estructuras de datos de la API son 
muy completas, existen librerías de terceros 
que las complementan
 Google Guava: 
 http://code.google.com/p/guava-libraries/
 Otras:
 http://java-source.net/open-source/collection-librarie
s
 
ESTRUCTURAS DE DATOS EN JAVA
http://code.google.com/p/guava-libraries/
http://java-source.net/open-source/collection-libraries
http://java-source.net/open-source/collection-libraries
92
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
PROGRAMACIÓN CONCURRENTE EN JAVA
93
Estructuras de datos sincronizadas
• En el Java Collections Framework existen 
implementaciones de las colecciones denominadas 
colecciones sincronizadas (synchronized collections)
• Estas implementaciones están diseñadas para que 
se puedan compartir entre varios hilos
• Tienen todos sus métodos bajo exclusión mútua 
(sincronizados)
• Ningún hilo podrá ejecutar ningún método mientras 
otro hilo esté ejecutando otro método (o el mismo) 
ESTRUCTURAS DE DATOS CONCURRENTES
94
Estructuras de datos sincronizadas
• Para crear una colección sincronizada primero hay que crear un 
objeto de una colección
• Luego se invoca algún método estático de la clase Collections
• A las colecciones sincronizadas se las denomina envolturas de 
sincronización (Synchronization Wrappers) porque evuelven a 
la colección original
ESTRUCTURAS DE DATOS CONCURRENTES
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <T> List<T> synchronizedList(List<T> list)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m)
public static <T> SortedSet<T> synchronizedSortedSet(SortedSet<T> s)
public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m)
95
Estructuras de datos sincronizadas
• Ejemplo de creación de colecciones sincronizadas
ESTRUCTURAS DE DATOS CONCURRENTES
List<String> sharedList = 
 Collections.synchronizedList(new ArrayList<>());
Map<String,String> sharedMap = 
 Collections.synchronizedMap(new HashMap<>());
Set<String> sharedSet = 
 Collections.synchronizedSet(new HashSet<>());
96
Estructuras de datos sincronizadas
• Ejemplo de 
acceso 
compartido a 
una lista de 
Strings
ESTRUCTURAS DE DATOS CONCURRENTES
public class SynchronizedCollectionsSample {
 private List<String> sharedList;
 private void process(int num) {
 for (int i = 0; i < 5; i++) {
 sharedList.add("H"+num+"_I"+i);
 }
 }
 public exec() {
 sharedList = 
 Collections.synchronizedList(new ArrayList<>());
 //Create threads and wait for it to finish
 
 System.out.println("List: "+sharedList);
 }
}
97
Instrucciones atómicas
• ¿El siguiente código es seguro si list es una colección 
sincronizada compartida por varios hilos?
ESTRUCTURAS DE DATOS SINCRONIZADAS
public String deleteLast(List<String> list) {
 int lastIndex = list.size() - 1;
 return list.remove(lastIndex);
}
98
Instrucciones atómicas
• ¿El siguiente código es seguro si list es una colección 
sincronizada compartida por varios hilos?
• La colección está preparada para llamadas concurrentes 
porque está sincronizada
• Pero es posible que se produzca una condición de carrera y la 
colección compartida sea modificada entre la primera 
consultar el tamaño y devolver el elemento
ESTRUCTURAS DE DATOS SINCRONIZADAS
public String deleteLast(List<String> list) {
 int lastIndex
= list.size() - 1;
 return list.remove(lastIndex);
}
99
Instrucciones atómicas
• Cualquier instrucción atómica de grano grueso, en la 
que se realice una acción compuesta requiere que el 
cliente de la colección sincronice su acceso a la misma
 Recorrer la colección
 Borrar/Devolver el último elemento
 Operaciones condicionales como “insertar si ausente” (put-if-
absent)
 …
• El cliente tiene que poner las acciones compuestas bajo 
exclusión mutua con el mismo cerrojo (lock) que se 
sincroniza internamente la colección
ESTRUCTURAS DE DATOS SINCRONIZADAS
100
Instrucciones atómicas
• Borrar el último elemento de una lista sincronizada
• Recorrer una lista sincronizada
ESTRUCTURAS DE DATOS SINCRONIZADAS
synchronized(sharedList) {
 for (String elem : sharedList){
 System.out.print(elem+",");
 }
 System.out.println();
}
public String deleteLast(List<String> list) {
 synchronized (list) {
 int lastIndex = list.size() - 1;
 return list.remove(lastIndex);
 }
}
101
Instrucciones atómicas
• Insertar un elemento en un mapa sincronizado 
si no existen ya antes
ESTRUCTURAS DE DATOS SINCRONIZADAS
synchronized (map) {
 if (!map.containsKey(key)) {
 map.put(key, value);
 }
}
102
Instrucciones atómicas
• Al recorrer un mapa sincronizado hay que usar como cerrojo 
(lock) al mapa, no a las colecciones auxiliares
• Si se recorre una colección sin exclusión mutua y otro hilo la 
modifica antes de finalizar el recorrido, se puede producir una 
ConcurrentModificationException
ESTRUCTURAS DE DATOS SINCRONIZADAS
synchronized(sharedMap) {
 for (Entry<String,Integer> elem : sharedMap.entrySet()){
 System.out.print(
 elem.getKey()+">"+elem.getValue()+",");
 }
 System.out.println();
}
103
Problemas con las colecciones sincronizadas
• Todas las acciones que se realizan sobre la colección 
deben sincronizarse (están bajo exclusión mutua)
• Esto puede limitar innecesariamente la concurrencia 
en operaciones de lectura simultáneas (que no causan 
interferencias)
• Se limita el aprovechamiento de los recursos, se 
limita la escalabilidad
• Esto es especialmente problemático al poner la 
colección bajo exclusión mutua para recorrerla y 
realizar operaciones en los elementos
ESTRUCTURAS DE DATOS SINCRONIZADAS
104
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
105
Estructuras de datos concurrentes
• Las colecciones concurrentes son 
implementaciones de las colecciones 
diseñadas para compartirse entre hilos
• Para ciertos casos, son más eficientes que las 
colecciones sincronizadas
ESTRUCTURAS DE DATOS CONCURRENTES
106
Estructuras de datos concurrentes
• Algunas de las colecciones concurrentes más 
usadas:
 Map: ConcurrentHashMap
 List: CopyOnWriteArrayList
 Set: CopyOnWriteArraySet , set basado en 
ConcurrentHashMap
ESTRUCTURAS DE DATOS CONCURRENTES
107
ConcurrentHashMap
• La clase ConcurrentHashMap está diseñada para 
permitir acceso concurrente de lectura y escritura
• Es mucho más eficiente que el mapa sincronizado y 
se recomienda su uso siempre
ESTRUCTURAS DE DATOS CONCURRENTES
Threads ConcurrentHashMap Mapa sincronizado
1 1.00 1.03
4 5.58 78.23
8 13.21 163.48
32 57.27 778.41
http://www.ibm.com/developerworks/library/j-jtp07233/
http://www.ibm.com/developerworks/library/j-jtp07233/
108
ConcurrentHashMap
• En las colecciones concurrentes no se puede 
usar la sincronización para implementar 
operaciones atómicas de grano grueso
• Para solucionarlo, el interfaz ConcurrentMap 
proporciona las operaciones atómicas de grano 
grueso más habituales como métodos
ESTRUCTURAS DE DATOS CONCURRENTES
109
ConcurrentHashMap
•Métodos de ConcurrentMap
 V putIfAbsent(K key, V value): Si la clave especificada no 
está asociada con ningún valor, se asocia con el valor 
indicado. Se devuelve el antiguo valor si existía.
 boolean remove(Object key, Object value): Borra la entrada 
para la clave sólo si está actualmente asociada al valor 
indicado
 V replace(K key, V value): Reemplaza la entrada para la clave 
sólo si está actualmente asociada a algún valor
 Muchos más…
ESTRUCTURAS DE DATOS CONCURRENTES
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html 
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ConcurrentHashMap.html
110
• Asociar un valor a una clave sólo si no existe esa clave 
en el mapa
synchronized (map) {
 if (!map.containsKey(key)){
 map.put(key, value);
 } else {
 // Ya existe la clave
 }
}
ConcurrentHashMap
ESTRUCTURAS DE DATOS CONCURRENTES
Object old = map.putIfAbsent(key, value);
if(old != null){
 // Ya existe la clave
}
synchronizedMap
ConcurrentHashMap
111
CopyOnWriteArrayList
• CopyOnWriteArrayList es una implementación 
concurrente de una lista
• Es más eficiente que la lista sincronizada si hay 
muchas lecturas y muy pocas escrituras
• Las colecciones copia-al-escribir (copy-on-write) 
no bloquean los hilos que leen. 
• Cuando un hilo quiere escribir, se crea una copia 
interna de los elementos (muy costoso), por eso 
sólo se usan con pocas escrituras.
ESTRUCTURAS DE DATOS CONCURRENTES
112
Estructuras de datos concurrentes
• Colecciones concurrentes para conjuntos
 La clase CopyOnWriteArraySet es similar a 
CopyOnWriteArrayList pero para conjuntos
 Siempre se puede usar un ConcurrentMap como si 
fuera un conjunto, considerando las claves (keys) 
del map como los valores del conjunto (y usando 
cualquier valor)
ESTRUCTURAS DE DATOS CONCURRENTES
113
Estructuras de datos concurrentes
• Colecciones concurrentes para conjuntos
 Si necesitamos ver ese mapa como un conjunto, 
podemos “recubrirlo”
 Si queremos usar las operaciones atómicas de 
grano grueso, lo tendremos que hacer sobre el 
mapa concurrente
ESTRUCTURAS DE DATOS CONCURRENTES
ConcurrentMap<String,Boolean> map = new ConcurrentHashMap<>();
Set<String> names = Collections.newSetFromMap(map); 
114
Ejercicio 4
• Se desea implementar de forma concurrente un 
programa que busca ficheros con el mismo nombre 
dentro de una carpeta
• La búsqueda se realiza recursivamente en unas 
carpetas dentro de otras
• Se proporciona la versión secuencial del programa
• Por simplicidad, en la carpeta raíz no hay ficheros y se 
crearán tantos hilos como carpetas
ESTRUCTURAS DE DATOS CONCURRENTES
115
Ejercicio 4
ESTRUCTURAS DE DATOS CONCURRENTES
public class FindDuplicates {
private Map<String,String> duplicates = new HashMap<>(); 
public void findDuplicates(File root) {
if (root.isDirectory()) {
for (File file : root.listFiles()) {
if (file.isDirectory()) {
findDuplicates(file);
} else {
String path = duplicates.get(file.getName());
if(path == null){
duplicates.put(file.getName(), file.getAbsolutePath());
} else {
System.out.println("Found duplicate file: "+file.getName());
System.out.println(" "+path);
System.out.println(" "+file.getAbsolutePath());
}
}
}
}
}
public void exec() {
findDuplicates(new File("X:\\Dir"));
}
}
116
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
117
Colas (Queues)
• Las colas (queues) son colecciones que 
albergan elementos antes de ser procesados
• Las colas son clases que implementan el 
interfaz java.util.Queue<E>
• El orden de procesado de elementos puede ser 
FIFO, por prioridades… y depende de la 
implementación
ESTRUCTURAS DE DATOS CONCURRENTES
118
Colas (Queues)
• Proporciona métodos para insertar, extraer e 
examinar elementos (sin extraerlos)
• Los métodos se proporcionan de dos formas
 Métodos que lanzan excepciones si fallan
 Métodos que devuelven false o null si fallan
ESTRUCTURAS DE DATOS CONCURRENTES
Lanza una 
excepción
Devuelve false 
o null
Insertar add(e) offer(e)
Extraer remove() poll()
Examinar element() peek()
119
Colas (Queues)
• Insertar
 Los métodos offer(e) y add(e) insertan un elemento 
si la cola no está llena
 Si la cola está llena:
 offer(e) devuelve false 
 add(e) lanza una excepción no chequeada
ESTRUCTURAS DE DATOS CONCURRENTES
120
Colas (Queues)
• Extraer
 Los métodos poll() y remove() extraen elementos 
de la cabeza si la cola no está vacía
 El orden de extracción depende de la política de 
ordenación de la cola (FIFO, Prioridad…)
 Si la cola está vacía:
 poll() devuelve null
 remove() lanza una excepción no chequeada
ESTRUCTURAS DE DATOS CONCURRENTES
121
Colas (Queues)
• Examinar
 Los métodos peek() y element() devuelven, pero no 
eliminan, la cabeza de la cola, si la cola no está vacía
 Si la cola está vacía:
 peek() devuelve null 
 element() lanza una excepción no chequeada
ESTRUCTURAS DE DATOS CONCURRENTES
122
Colas (Queues)
• Las colas se utilizan habitualmente para 
implementar esquemas de productores 
consumidores en programación concurrente
• Para ello es necesario que los métodos para 
extraer y añadir sean bloqueantes 
• El interfaz de colas (Queue) no define métodos 
bloqueantes 
• Estos métodos están definidos en el interfaz 
BlockingQueue, que hereda de Queue
ESTRUCTURAS DE DATOS CONCURRENTES
123
Colas bloqueantes (BlockingQueue)
• Las colas bloqueantes 
(java.util.concurrent.BlockingQueue) ofrecen 
operaciones que se quedan bloqueadas
• Si la cola está llena, los métodos de inserción 
se bloquean hasta que haya espacio (o salte 
un timeout)
• Si la cola está vacía, los métodos de extracción 
se bloquean hasta que exista un elemento (o 
salte un timeout)
ESTRUCTURAS DE DATOS CONCURRENTES
124
Colas bloqueantes (BlockingQueue)
• Inserción bloqueante
 put(e): Bloquea hasta que se pueda realizar la 
operación
 offer(e, time, unit): Bloquea y devuelve false si no 
se realiza la operación en el tiempo indicado
• Extracción bloqueante
 take(): Bloquea hasta que se pueda realizar la 
operación
 poll(time, unit): Bloquea y devuelve null si no se 
realiza la operación en el tiempo indicado
ESTRUCTURAS DE DATOS CONCURRENTES
125
Colas bloqueantes (BlockingQueue)
• Las colas bloqueantes (BlockingQueue) son 
clases que permiten compartirse por la varios 
hilos (thread-safe) 
• Pero algunas operaciones compuestas como 
addAll, containsAll,… puede que no se 
implementen de forma atómica (conviene 
revisar la documentación de cada método)
ESTRUCTURAS DE DATOS CONCURRENTES
126
Colas dobles (Deque)
• Las colas dobles (java.util.Deque) son 
colecciones lineales que permiten inserción y 
eliminación en cualquier extremo
• Deque proviene de cola doblemente finalizada 
(double ended queue)
• Este estructura se puede comportar como una 
cola FIFO o como una pila LIFO dependiendo 
de los métodos usados
• El interfaz Deque hereda de Queue
ESTRUCTURAS DE DATOS CONCURRENTES
127
ESTRUCTURAS DE DATOS CONCURRENTES
128
Implementaciones de Queue
• Queue
 PriorityQueue
 Ordenación basada en prioridades
 No concurrente (no thread-safe)
 ConcurrentLinkedQueue
 Ordención FIFO
 Concurrente (thread-safe)
 LinkedList
 Ordenación FIFO
 No concurrente (no thread-safe)
ESTRUCTURAS DE DATOS CONCURRENTES
129
Implementaciones de Deque
• Deque
 ArrayDeque
 Implementación FIFO basada en array
 No concurrente (no thread-safe)
 LinkedList
 Implementación FIFO basada en lista enlazada
 No concurrente (no thread-safe)
 ConcurrentLinkedDeque (Java 7)
 Implementación FIFO basada en lista enlazada 
 Concurrente (thread-safe)
ESTRUCTURAS DE DATOS CONCURRENTES
130
Implementaciones de BlockingQueue
• BlockingQueue
 ArrayBlockingQueue
 Cola FIFO basada en arrays
 LinkedBlockingQueue
 Cola FIFO basada en listas enlazadas
 PriorityBlockingQueue
 Cola por prioridades
ESTRUCTURAS DE DATOS CONCURRENTES
131
Implementaciones de BlockingQueue
• BlockingQueue
 SynchronousQueue
 Cola sin capacidad
 Las inserciones deben esperar a las extracciones
 Las extracciones deben esperar a las inserciones
 DelayQueue
 Cola FIFO que mantiene los elementos en la cola 
durante un tiempo especificado (delay) antes de que 
estén disponibles para la extracción
ESTRUCTURAS DE DATOS CONCURRENTES
132
Implementaciones
• BlockingDeque
 LinkedBlockingDeque
 Cola doble basada en listas enlazas
 Concurrente (thread-safe)
• TransferQueue
 Cola con métodos para esperar a consumidores al insertar 
un elemento
 LinkedTransferQueue
 Cola de transferencia basada en listas enlazadas
 Concurrente (thread-safe)
ESTRUCTURAS DE DATOS CONCURRENTES
133
Uso de las colas
• Las colas se 
utilizan para 
esquemas de 
productores-
consumidores
ESTRUCTURAS DE DATOS CONCURRENTES
public class ProdConsQueue {
private BlockingQueue<Integer> queue 
= new ArrayBlockingQueue<>(10);
public void productor() throws InterruptedException{
for (int i = 0; i < 20; i++) {
Thread.sleep((long)(Math.random() * 500));
queue.put(i);
}
}
public void consumidor() throws InterruptedException{
while (true) {
int data = queue.take();
Thread.sleep((long)(Math.random() * 500));
System.out.println(data + " ");
}
}
public void exec() {
//Crear hilos productores y consumidores //
}
}
134
Ejercicio 5
• Se pretende simular mediante un programa concurrente el 
comportamiento de una Fábrica de coches.
• La fábrica tiene dos tipos de elementos:
 Máquinas: Que generan piezas de un determinado tipo.
 Robots: Que ensamblan todas las piezas que forman el coche.
• Para hacer el programa más genérico, se utilizan las siguientes 
constantes:
 NUM_ROBOTS: Número de robots
 NUM_TIPOS_PIEZAS: Tipos diferentes de piezas que se necesitan 
para montar un coche. Cada pieza tiene asociado su tipo y es un valor 
entre 0 y NUM_TIPOS_PIEZAS-1
ESTRUCTURAS DE DATOS CONCURRENTES
135
Ejercicio 5
•Máquinas
 Las máquinas fabrican piezas y las almacenan. Este proceso se 
repite indefinidamente.
 Las piezas se simulan como un valor aleatorio de tipo double
 Los métodos que se simulan estas operaciones son: 
fabricarPieza(…) y almacenarPieza(…)
 Cada máquina sólo genera piezas del mismo tipo. Hay una máquina 
por cada tipo de pieza. 
 El almacén donde se guardan las piezas es común para todas las 
máquinas. 
 El almacén de piezas puede albergar un máximo de piezas de cada 
tipo (identificado por la constante MAX_PIEZAS )
ESTRUCTURAS DE DATOS CONCURRENTES
136
Ejercicio 5
• Robots
 Los robots recogen piezas del almacén y las 
montan para crear los coches. Este proceso se 
repite indefinidamente.
 Los métodos que simulan estas operaciones son: 
recogerPieza(…) y montarPieza(…)
 Las piezas deben recogerse en orden (de 1 a 
NUM_TIPOS_PIEZAS) para que se puedan montar 
adecuadamente.
ESTRUCTURAS DE DATOS CONCURRENTES
137
Ejercicio 5
• Requisitos
 Las máquinas pueden fabricar piezas y los robots 
pueden montar piezas en el producto sin sufrir 
ninguna interferencia entre ellos
 El programa debe implementarse de forma que ni 
las máquinas ni los robots interfieran entre sí de 
forma inadecuada al acceder al almacén
ESTRUCTURAS DE DATOS CONCURRENTES
138
Estructuras de datos concurrentes
• Introducción
• Valores atómicos concurrentes
• Estructuras de datos en Java
• Estructuras de datos sincronizadas
• Estructuras de datos concurrentes
• Colas (Queues)
• Streams
PROGRAMACIÓN CONCURRENTE EN JAVA
139
Streams
• Los streams (flujos) permiten procesar 
colecciones de elementos con un estilo 
funcional (declarativo)
• Ventajas frente al estilo actual:
 Más compacto y de alto nivel
 Fácilmente paralelizable
 No se necesita tener todos los elementos en 
memoria para empezar a procesar
ESTRUCTURAS DE DATOS CONCURRENTES
140
Streams
• Tenemos la clase Person y la lista people
ESTRUCTURAS DE DATOS CONCURRENTES
public class Person { 
 
 private String name; 
 private int age; 
 // constructors 
 // getters / setters 
} 
List<Person> people = new ArrayList<>();
141
Streams
• Se quiere calcular la edad media
ESTRUCTURAS DE DATOS CONCURRENTES
int sum = 0;
int average = 0; 
for (Person person : people) {
 sum += person.getAge();
}
if (!list.isEmpty()) {
 average = sum / list.size();
}
142
Streams
• Se quiere calcular la edad media de los menores de 20
ESTRUCTURAS DE DATOS CONCURRENTES
int sum = 0; 
int n = 0; 
int average = 0; 
for (Person person : people) { 
 if (person.getAge() > 20) { 
 n+ + ; 
 sum + = person.getAge() ; 
 } 
} 
if (n > 0) { 
 average = sum / n ; 
} 
143
Streams
• La programación imperativa es muy verbosa porque 
se indica qué se quiere y cómo se consigue
• La programación declarativa es mucho más 
compacta porque basta indicar qué se quiere
ESTRUCTURAS DE DATOS CONCURRENTES
select 
avg(age) 
from Person 
where age > 20 
Sentencia SQL equivalente
144
Streams
ESTRUCTURAS DE DATOS CONCURRENTES
Mapeo (mapping):
De cada elemento de 
la lista original obtiene 
un nuevo valor para la 
lista resultante. 
Se devuelve el mismo 
número de elementos
Filtrado: 
Filtra los elementos. 
Se devuelven menos 
elementos de los 
originales
Reducción: 
Se agregan todos los 
elementos en uno solo: 
Ejemplos: media, 
mínimo, máximo, 
suma…
145
Streams
• Los streams (flujos) permiten aplicar 
operaciones de mapeo, filtrado y reducción a 
colecciones de datos
• Un stream es una secuencia de elementos que 
sólo pueden procesarse una vez
• Puede generarse de forma dinámica (no tiene 
que estar toda la secuencia en memoria)
• Esto permite implementaciones muy 
eficientes de las operaciones sin crear 
estructuras de datos intermedias
ESTRUCTURAS DE DATOS CONCURRENTES
146
Streams
• La forma más habitual de crear un stream es 
partiendo de una colección
ESTRUCTURAS DE DATOS CONCURRENTES
int sumOver20 = persons.stream()
 .map(Person::getAge)
 .filter(age -> age > 20)
 .reduce(0, Integer::sum);
Creación del 
stream
147
Streams
• También se pueden crear de forma literal:
• Partiendo de un array:
ESTRUCTURAS DE DATOS CONCURRENTES
Stream<String> s = 
Stream.of("one","two","three");
Stream<String> s2 = Stream.empty();
String[] numbers = {"one", "two", "three"};
Stream<String> s = Arrays.stream(numbers);
148
Streams
• O desde algunos métodos de la API:
ESTRUCTURAS DE DATOS CONCURRENTES
IntStream chars = “Hola”.chars();
Stream<String> lines = lineNumberReader.lines();
IntStream nums = random.ints();
Stream s3 = Stream.concat(stream1, stream2);
Stream<Integer> pares = Stream.iterate(0,n->n+2);
Stream<Double> rand = 
Stream.generate(Math::random);
149
Streams
ESTRUCTURAS DE DATOS CONCURRENTES
Stream Colección
Secuencia de elementos Elementos que se pueden 
procesar en secuencia o con 
acceso directo (get)
Se pueden calcular según se van 
procesando
Tienen que estar en memoria 
antes de procesarse
Información volátil que sólo se 
puede procesar una vez
Estructuras de datos en 
memoria
Tamaño infinito Tamaño finito
Tiene versiones eficientes para 
tipos primitivos (IntStream, 
DoubleStream, LongStream)
 No tiene versiones para tipos 
primitivos
150
Streams
• Operaciones que se pueden realizar sobre un 
stream:
 1) Operaciones intermedias
 Se procesan de forma perezosa (sólo si son necesarias)
 Puede haber varias (incluso del mismo tipo)
 Ejemplos: map, filter, skip,…
 2) Operaciones terminales
 Inician la computación y devuelven un objeto, un valor, 
una lista o nada…
 Sólo puede haber una al final
 Una vez aplicada, el stream no se puede reutilizar
 Ejemplos: sum, find, min, toArray…
ESTRUCTURAS DE DATOS CONCURRENTES
151
Streams
• Operaciones que se pueden realizar sobre un 
stream:
ESTRUCTURAS DE DATOS CONCURRENTES
int sumOver20 = 
persons.stream()
 .map(Person::getAge)
 .filter(age -> age > 20)
 .reduce(0, Integer::sum);
Operaciones 
intermedias
Operación
terminal
152
Streams
• Operaciones intermedias:
 filter: quita algunos elementos
 map: por cada elemento obtiene un nuevo valor:
 sorted: ordena
 peek: aplica una operación a cada elemento
 distinct: filtra dejando los distintos
 limit: limita el número de elementos
 skip: ignora los primeros elementos
 range: devuelve un rango de los elementos (from, to)
ESTRUCTURAS DE DATOS CONCURRENTES
153
Streams
• Operaciones terminales en Stream<T>:
 count: Cuenta los elementos.
 min, max: Obtiene el mínimo el y máximo
 anyMatch, allMatch, noneMatch(): Indica si se cumple (o no) el 
criterio.
 findFirst, findAny: Devuelve el elemento que cumpla el criterio
 mapToInt: Convierte a IntStream
 toArray: Devuelve el contenido como array
 forEach, forEachOrdered: Ejecuta por cada elemento
 reduce: Mecanismo genérico de reducción de todos los elementos
 collect: Mecanismo genérico para “recolectar” los elementos
ESTRUCTURAS DE DATOS CONCURRENTES
154
Streams
• Operaciones terminales en streams numéricos 
(IntStream, LongStream, DoubleStream):
 average(): Calcula la media
 sum(): Suma los elementos
 summaryStatistics(): Calcula estadísticas de los datos 
(media, cuenta, min, max, sum)
ESTRUCTURAS DE DATOS CONCURRENTES
155
Streams
• Operación terminal Collect (Recolectar)
 La operación collect es un mecanismo genérico para 
implementar operaciones terminales
 Se puede implementar cualquier política de recolección de los 
elementos:
 Devolver una estructura de datos (List, Set, Map…)
 Fusionar todos los elementos en un String
 Obtener un valor (suma, media, min, max…)
 La clase Collectors tiene métodos estáticos para crear una 
infinidad de recolectores
ESTRUCTURAS DE DATOS CONCURRENTES
ArrayList<String> l = stream.collect(Collectors.toList());
156
Streams
• Edad media
• Número de personas mayores de 20
• Persona más mayor (si existen)
ESTRUCTURAS DE DATOS CONCURRENTES
double avgAge = persons.stream()
 .collect(Collectors.averagingDouble(Person::getAge));
int num = persons.stream().filter(p -> p.getAge() > 20)
 .collect(Collectors.counting());
Optional<Person> older = persons.stream() 
.collect(Collectors.maxBy(Comparator.comparing(Person::getAge));
157
Streams
• Devolver los nombres en una lista
• Devolver los nombres sin repeticiones ordenados (TreeSet)
• Devolver los nombres separados por comas
ESTRUCTURAS DE DATOS CONCURRENTES
List<String> list = people.stream().map(Person::getName)
 .collect(Collectors.toList());
Set<String> set = people.stream().map(Person::getName)
 .collect(Collectors.toCollection(TreeSet::new));
String joined = things.stream().map(Object::toString)
 .collect(Collectors.joining(", "));
158
Streams
• Suma de salarios de los empleados
• Agrupar por departamento
• Suma de salarios por departamento
ESTRUCTURAS DE DATOS CONCURRENTES
int total = employees.stream()
 .collect(Collectors.summingInt(Employee::getSalary)));
Map<Department, List<Employee>> byDept = 
employees.stream()
 
.collect(Collectors.groupingBy(Employee::getDepartment));
Map<Department, Integer> totalByDept = employees.stream()
 .collect(Collectors.groupingBy(
 Employee::getDepartment,
 Collectors.summingInt(Employee::getSalary)));
159
Streams
• Suma de salarios por departamento
• Dividir los estudiantes entre aprobados
y suspensos
ESTRUCTURAS DE DATOS CONCURRENTES
Map<Department, Integer> totalByDept = employees.stream()
 .collect(
 Collectors.groupingBy(
 Employee::getDepartment,
 Collectors.summingInt(Employee::getSalary)
 )
 );
Map<Boolean, List<Student>> passingFailing = students.stream()
 .collect(
 Collectors.partitioningBy(s->s.getGrade() >= PASS_THR)
 );
160
Procesamiento en paralelo de streams
• La principal ventaja de los streams es que 
especificamos las operaciones que queremos 
que se realicen, pero no especificamos cómo 
deben realizarse.
• Por defecto las operaciones se ejecutan de 
forma secuencial en el hilo de ejecución, pero 
podemos pedir que se ejecuten en paralelo
STREAMS
161
Procesamiento en paralelo de streams
• Partiendo de un stream se puede obtener su stream 
paralelo
• Todas las operaciones que se ejecutarán de forma 
automática en paralelo, dividiendo las tareas en 
diferentes hilos
STREAMS
Stream<String> s = nombres.parallelStream();
Stream<String> s = nombres.stream().parallel();
162
Procesamiento en paralelo de streams
• Ejemplos de streams paralelos:
ESTRUCTURAS DE DATOS CONCURRENTES
List<Integer> even = numbers.stream()
 .filter(n -> n % 2 == 0)
 .sorted()
 .collect(Collectors.toList());
Secuencial
808ms
Paralelo
507ms
List<Integer> even = 
numbers.parallelStream()
 .filter(n -> n % 2 == 0)
 .sorted()
 .collect(Collectors.toList());
163
Procesamiento en paralelo de streams
• Por defecto se usan tantos hilos como procesadores 
tiene el sistema, porque se asume que las operaciones 
no realizan ninguna operación bloqueante
• Si se quiere usar un número diferente de hilos:
ESTRUCTURAS DE DATOS CONCURRENTES
List<Integer> even = 
new ForkJoinPool(NUM_THREADS).submit(()-> 
 numbers.stream()
 .filter(n -> n % 2 == 0)
 .sorted()
 .collect(Collectors.toList()
).get();
164
Procesamiento en paralelo de streams
• Cuidado al usar streams paralelos
 Paralelizar un algoritmo tiene un coste de gestión 
asociado (división del trabajo, gestión de los hilos, 
consolidación de resultados, etc…)
 Para pocos datos o para ciertos tipos de 
algoritmos, no merece la pena paralelizar porque 
sería más lento.
 Es importante comparar la versión secuencial y la 
paralela para unos datos de entrada habituales
ESTRUCTURAS DE DATOS CONCURRENTES
https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html 
https://docs.oracle.com/javase/tutorial/collections/streams/parallelism.html
		Slide 1
		Slide 2
		Estructuras de datos concurrentes
		Introducción
		Introducción
		Introducción
		Estructuras de datos concurrentes
		Valores atómicos concurrentes
		AtomicInteger
		AtomicInteger
		Estructuras de datos concurrentes
		Estructuras de datos en Java
		Estructuras de datos en Java
		Estructuras de datos en Java
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Genéricos
		Estructuras de datos en Java
		Listas, conjuntos y mapas
		Listas, conjuntos y mapas
		Listas, conjuntos y mapas
		Listas, conjuntos y mapas
		Slide 34
		Slide 35
		Slide 36
		Slide 37
		Listas, conjuntos y mapas
		Listas, conjuntos y mapas
		Slide 40
		Listas (List<E>)
		Listas (List<E>)
		Listas (List<E>)
		Ejercicio 1
		Conjuntos (Set<E>)
		Conjuntos (Set<E>)
		Conjuntos (Set<E>)
		Mapas (Map<K,V)
		Mapas (Map<K,V)
		Mapas (Map<K,V)
		Mapas (Map<K,V)
		Ejercicio 2
		Estructuras de datos ordenadas
		Otras implementaciones
		Estructuras de datos en Java
		Recorrer una colección
		Recorrer una Lista
		Recorrer una Lista
		Recorrer una Lista
		Recorrer una Lista
		Recorrer una Lista
		Recorrer un Conjunto
		Recorrer un Mapa
		Recorrer un Mapa
		Recorrer un Mapa
		Estructuras de datos en Java
		Ordenación y Búsqueda
		Ordenación y Búsqueda
		Ordenación y Búsqueda
		Ordenación y Búsqueda
		Slide 71
		Ordenación y Búsqueda
		Ordenación y Búsqueda
		Ordenación y Búsqueda
		Ejercicio 3
		Estructuras de datos en Java
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Equals y hashcode
		Estructuras de datos en Java
		Colecciones con tipos primitivos
		Colecciones con tipos primitivos
		Colecciones con tipos primitivos
		Estructuras de datos en Java
		Ventajas de las colecciones
		Ventajas de las colecciones
		Estructuras de datos concurrentes
		Estructuras de datos sincronizadas
		Estructuras de datos sincronizadas
		Estructuras de datos sincronizadas
		Estructuras de datos sincronizadas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Instrucciones atómicas
		Problemas con las colecciones sincronizadas
		Estructuras de datos concurrentes
		Estructuras de datos concurrentes
		Estructuras de datos concurrentes
		ConcurrentHashMap
		ConcurrentHashMap
		ConcurrentHashMap
		ConcurrentHashMap
		CopyOnWriteArrayList
		Estructuras de datos concurrentes
		Estructuras de datos concurrentes
		Ejercicio 4
		Ejercicio 4
		Estructuras de datos concurrentes
		Colas (Queues)
		Colas (Queues)
		Colas (Queues)
		Colas (Queues)
		Colas (Queues)
		Colas (Queues)
		Colas bloqueantes (BlockingQueue)
		Colas bloqueantes (BlockingQueue)
		Colas bloqueantes (BlockingQueue)
		Colas dobles (Deque)
		Slide 127
		Implementaciones de Queue
		Implementaciones de Deque
		Implementaciones de BlockingQueue
		Implementaciones de BlockingQueue
		Implementaciones
		Uso de las colas
		Ejercicio 5
		Ejercicio 5
		Ejercicio 5
		Ejercicio 5
		Estructuras de datos concurrentes
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Streams
		Procesamiento en paralelo de streams
		Procesamiento en paralelo de streams
		Procesamiento en paralelo de streams
		Procesamiento en paralelo de streams
		Procesamiento en paralelo de streams
__MACOSX/Academia/Tema5/._Tema 5.6 - Estructuras de datos concurrentes.pdf
Academia/Tema5/.DS_Store
__MACOSX/Academia/Tema5/._.DS_Store
Academia/Tema5/Tema 5.1 - Introducción updated.pdf
Programación	
  
Concurrente	
  en	
  Java
Programación	
  Concurrente	
  – Tema	
  5
Miguel	
  Ángel	
  Rodríguez	
  García
Carlos	
  Grima
Lucía	
  Serrano	
  Luján
Universidad	
  Rey	
  Juan	
  Carlos
Curso	
  2017	
  -­‐ 2018
Introducción
Programación	
  Concurrente	
  en	
  Java	
  -­‐
Tema	
  5.1
Introducción
•Modelo  de  concurrencia
§ Java  ofrece  un  modelo  de  concurrencia  de  memoria  
compartida integrado  en  el  lenguaje  Java  y  en  la  
librería  estándar
§ El  soporte  de  concurrencia  ha  evolucionado  mucho  
en  Java
§ En  las  últimas  versiones  existen  herramientas  de  
alto  nivel  en  la  librería  estándar  que  permiten  al  
desarrollador  abstraerse  de  detalles  de  bajo  nivel
Tema  5  -­‐ Programación  Concurrente  en  Java 3
PROGRAMACIÓN CONCURRENTE EN JAVA
Introducción
•La  concurrencia  también  está  integrada  en  el  
propio  lenguaje  de  programación
§ Está  definido  cómo  se  comparte  la  memoria  entre  
diferentes  hilos  en  el  Modelo  de  Memoria  de  Java  [1]  
(Java  Memory Model)
§ Existen  palabras  reservadas  en  el  propio  lenguaje  
relacionadas  con  la  concurrencia,  por  ejemplo  para  
delimitar  zonas  de  exclusión  mutua  se  usa  
synchronized
Tema  5  -­‐ Programación  Concurrente  en  Java 4
PROGRAMACIÓN CONCURRENTE EN JAVA
[1]  http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-­‐133-­‐faq.html
Introducción
•También  tiene  soporte  en  la
librería  estándar
§ Como  Java  es  un  lenguaje  orientado  a  objetos,  los  
hilos  se  representan  como  objetos  de  la  clase  
java.lang.Thread
§ Esta  clase  tiene  métodos  para  controlar  el  ciclo  de  
vida  del  hilo (configurar  su  nombre,  prioridad,  
iniciar,  esperar  a  que  finalice,  etc…)
§ En  Java  los  hilos  se  pueden  crear  y  destruir en  
cualquier  momento de  la  ejecución  del  programa,  
no tienen  que  seguir  la  rígida  estructura de  los  
programas  de  SimpleConcurrent
Tema  5  -­‐ Programación  Concurrente  en  Java 5
PROGRAMACIÓN CONCURRENTE EN JAVA
Historia  de  la  Concurrencia  en  Java
•En  las  sucesivas  versiones  de  la  plataforma  Java  
el  soporte  de  programación  concurrente ha  
avanzado  considerablemente
•Es  muy  importante  tener  en  cuenta  la  evolución  
de  las  herramientas  de  concurrencia  en  Java  
cuando  se  revise  documentación  (para  evitar  
que  esté  obsoleta)
Tema  5  -­‐ Programación  Concurrente  en  Java 6
INTRODUCCIÓN
Historia  de  la  Concurrencia  en  Java
Tema  5  -­‐ Programación  Concurrente  en  Java 7
INTRODUCCIÓN
Año Versión Java Concurrencia
1995 Java  1.02 Hilos y  monitores (exclusión mutua y  sinc condicional)
1996 Java  1.1
1998 Java  2  1.2 Estructuras  de  datos  concurrentes.  Se  desaconsejó  el  uso  de  ciertas  
funciones  de  gestión  de  hilos  por  ser  inseguras  (stop,  pause,  resume)
2000 Java  2  1.3
2002 Java  2  SE  1.4
2004 Java  2  SE  1.5  
o  5
Muchas  mejoras  en  concurrencia:  Java  Memory Model,  Ejecutores,  Colas  de  
procesamiento,  Temporizadores,  Mejores  estructuras  de  datos  concurrentes
2006 Java  SE  6 Colas  de  procesamiento mejoradas
2011 Java  SE  7 Framework  Fork/Join
2014 Java  SE  8 Mejoras en  estructuras de  datos concurrentes,  lambdas,  estructuras de  
datos con  procesamiento funcional (declarativo)  y  ejecución paralela,  
Futuros mejorados
2017 ..Java  SE  8 …  se  continúa actualizando.  Por ejemplo MAX_LOCKS  àMáximo de  
bloqueos simultáneos que  FileHandler puede manejar
Introducción
•En  java  se  pueden  usar  otros  modelos  de  
concurrencia  con  librerías
§ Actores:  
• Akka: http://akka.io/
•Quasar: http://www.paralleluniverse.co/quasar/
§ Memoria  software  transaccional:  
•Multiverse: http://multiverse.codehaus.org
§ Comunicando  procesos  secuenciales  (CSP):  
• JCSP: http://www.cs.kent.ac.uk/projects/ofa/jcsp/
§ Programación  funcional:  
• Framework  Collections en  Java  8:  
http://java.dzone.com/articles/title-­‐devoxx-­‐2012-­‐java-­‐8
• RxJava: https://github.com/Netflix/RxJava
Tema  5  -­‐ Programación  Concurrente  en  Java 8
PROGRAMACIÓN CONCURRENTE EN JAVA
Introducción
•Colección  de  libros  recomendables  para  dominar  la  
programación  concurrente, especialmente  para  
sistemas  de  memoria  compartida
Tema  5  -­‐ Programación  Concurrente  en  Java 9
PROGRAMACIÓN CONCURRENTE EN JAVA
http://software.intel.com/en-­‐us/articles/technical-­‐books-­‐for-­‐multi-­‐core-­‐software-­‐developers/
(y  muchos  más…)
2006 20041999
__MACOSX/Academia/Tema5/._Tema 5.1 - Introducción updated.pdf
Academia/Tema5/Tema 5.8 - Ejecución concurrente y asíncrona de tareas.pdf
Programación
Concurrente
Tema 5
Programación 
Concurrente en 
Java
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
Programación
Concurrente
Tema 5.6
Ejecución 
concurrente y 
asíncrona de tareas
Micael Gallego
micael.gallego@urjc.es
@micael_gallego
mailto:micael.gallego@urjc.es
3
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
4
Ejecución de tareas
• La mayoría de las aplicaciones concurrentes se 
estructuran en base a la ejecución de tareas
• Las tareas son unidades de trabajo abstractas 
e independientes entre sí
• La división en tareas tiene las siguientes 
ventajas:
 Simplifica la organización del programa
 Facilita la recuperación de errores
 Promueve la concurrencia porque permite 
paralelizar la ejecución de tareas
EJECUCIÓN DE TAREAS EN HILOS
5
Ejecución de tareas
• El ámbito de cada tarea tiene que seleccionarse 
de forma adecuada
• Las tareas deberían ser lo más independientes 
entre sí. Una tarea no debería depender del 
estado, el resultado o lo que realice otra tarea
• La independencia favorece la ejecución en 
paralelo (aprovechamiento de recursos)
• Para favorecer la escalabilidad y el balanceo de 
carga, las tareas no deberían ser muy grandes
EJECUCIÓN DE TAREAS EN HILOS
6
Ejecución de tareas
• En las aplicaciones web se considera que 
atender a una petición de un usuario 
representa una tarea
• Las tareas se ejecutan concurrentemente:
 Si una tarea tarda mucho en procesarse, no afecta 
a los demás usuarios
 Si existen varios procesadores, se podrán repartir 
el trabajo entre ellos cuando varios usuarios hagan 
peticiones concurrentes
EJECUCIÓN DE TAREAS EN HILOS
7
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
8
Ejecución secuencial de tareas
• Existen varias políticas para ejecutar las tareas 
de una aplicación
• La forma más sencilla de ejecutar tareas es 
hacerlo de forma secuencial
• Una aplicación que ejecuta tareas de forma 
secuencial sólo necesita un único hilo de 
ejecución (single thread)
EJECUCIÓN DE TAREAS EN HILOS
9
Ejecución secuencial de tareas
• Ejecución secuencial
EJECUCIÓN DE TAREAS EN HILOS
class ServidorWebUnicoHilo { 
 public static void main(String[] args) throws IOException {
 ServerSocket socket = new ServerSocket(80);
 while (true) { 
 Socket conexion = socket.accept(); 
 procesarPeticion(conexion); 
 } 
 } 
}
Se bloquea a la espera de una 
nueva petición http
10
Ejecución secuencial de tareas
• El problema con esta solución es que el 
servidor no puede aceptar una nueva 
conexión mientras está procesando la 
petición actual
• Además, si el servidor tiene varios 
procesadores o atender la petición requiere 
acceso de entrada/salida se estarán 
desaprovechando los recursos del sistema
EJECUCIÓN DE TAREAS EN HILOS
11
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
12
Ejecución de tareas en un nuevo hilo
• Para solucionar el problema, se puede ejecutar 
cada tarea en su propio hilo de ejecución
• De esa forma se pueden aceptar nuevas 
conexiones aunque se esté procesando la 
petición actual
• Se aprovechan mejor los recursos cuando se 
producen peticiones concurrentes 
EJECUCIÓN DE TAREAS EN HILOS
13
Ejecución de tareas en un nuevo hilo
• Nuevo hilo por cada tarea
EJECUCIÓN DE TAREAS EN HILOS
class ServidorWebHiloPorTarea { 
 public static void main(String[] args) throws IOException { 
 ServerSocket socket = new ServerSocket(80);
 while (true) { 
 Socket conexion = socket.accept();
 Runnable tarea = ()-> procesarPeticion(conexion); 
 new Thread(tarea).start(); 
 } 
 } 
} Se crea un hilo que ejecuta la 
tarea para atender la petición
14
Ejecución de tareas en un nuevo hilo
• Hay que tener en cuenta que ahora se pueden 
estar procesando varias peticiones de forma 
concurrente
• Si
se comparte algún objeto al procesar la 
petición, ese objeto debe ser thread-safe o 
estar apropiadamente sincronizado
EJECUCIÓN DE TAREAS EN HILOS
15
Ejecución de tareas en un nuevo hilo
• Problemas con la creación ilimitada de hilos
 Si existen pocas peticiones concurrentes, la creación de 
un hilo por petición puede ser aceptable
 Pero para un sistema en producción con mucha carga 
presenta varios problemas:
 La creación de un hilo es costosa
 Cada hilo activo consume memoria (para la pila de ejecución) y 
tiempo de CPU en cambios de contexto
 Está limitado el número máximo de hilos (depende del sistema 
operativo, JVM, configuraciones, etc…)
EJECUCIÓN DE TAREAS EN HILOS
16
Ejecución de tareas en un nuevo hilo
• Problemas con la creación ilimitada de hilos
 La aplicación puede funcionar hasta cierto límite y 
alcanzado ese límite puede fallar repentinamente 
por problemas de memoria
 Es mejor que se degrade el rendimiento de forma 
gradual y en un punto determinado que no se 
pueda atender a nuevos clientes 
EJECUCIÓN DE TAREAS EN HILOS
17
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
18
Framework de ejecución de tareas
• Las tareas son unidades lógicas de trabajo, y los 
hilos permiten ejecutar las tareas de forma 
asíncrona
• Ni la ejecución secuencial de tareas ni la creación 
de un hilo por tarea son soluciones aceptables
• La solución es limitar el número máximo de hilos 
posibles y reutilizar los hilos en vez de crear uno 
nuevo por cada tarea
EJECUCIÓN DE TAREAS EN HILOS
19
Framework de ejecución de tareas
• La biblioteca estándar de Java dispone de un 
interfaz para ejecutar tareas llamado 
Executor
• Existen varias clases que implementan el 
interfaz con diferentes políticas de ejecución 
EJECUCIÓN DE TAREAS EN HILOS
package java.util.concurrent;
public interface Executor { 
 void execute(Runnable task); 
}
20
Framework de ejecución de tareas
• Si se quisiera, se podrían implementar ejecutores que 
se comportaran como los ejemplos anteriores
EJECUCIÓN DE TAREAS EN HILOS
public class HiloPorTareaExecutor implements Executor {
 public void execute(Runnable tarea) { 
 new Thread(tarea).start(); 
 }; 
}
public class TareasSecuenciaExecutor implements Executor {
 public void execute(Runnable tarea) { 
 tarea.run(); 
 }; 
}
21
Framework de ejecución de tareas
EJECUCIÓN DE TAREAS EN HILOS
class ServidorWebHiloPorTarea { 
 private static Executor executor = 
 new HiloPorTareaExecutor();
 public static void main(String[] args) throws IOException { 
 ServerSocket socket = new ServerSocket(80); 
 while (true) {
 Socket conexion = socket.accept();
 Runnable tarea = ()-> procesarPeticion(conexion); 
 executor.execute(tarea);
 } 
 } 
}
Usamos el ejecutor para que la 
política de ejecución concreta la 
decida el ejecutor
22
Framework de ejecución de tareas
• Las tareas son unidades lógicas de trabajo, y los 
hilos permiten ejecutar las tareas de forma 
asíncrona
• Ni la ejecución secuencial de tareas ni la creación 
de un hilo por tarea son soluciones aceptables
• La solución es limitar el número máximo de hilos 
posibles y reutilizar los hilos en vez de crear uno 
nuevo por cada tarea
EJECUCIÓN DE TAREAS EN HILOS
23
Framework de ejecución de tareas
• La ventaja de usar un ejecutor es que se puede 
elegir de forma sencilla la política de ejecución
 Cuántas tareas concurrentemente (número máximo 
de hilos)
 Reutilización de hilos para varias tareas (evita crear 
un hilo por tarea)
 En qué orden se deben ejecutar (FIFO, prioridad)
 Cuántas tareas pendientes de ejecución
 …
EJECUCIÓN DE TAREAS EN HILOS
24
Framework de ejecución de tareas
• No existe una política de ejecución adecuada para 
todos los casos
• Depende de:
 Los recursos disponibles (número de cores, memoria)
 Si las tareas hacen mucho uso de entrada/salida 
bloqueante
 Si es preferible reducir el rendimiento para un cliente o 
bien rechazar a nuevos clientes
EJECUCIÓN DE TAREAS EN HILOS
25
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
26
Ejecución de tareas con grupos de hilos
• Grupos de hilos (Thread Pools)
 Executors implementados con un conjunto de hilos 
que ejecutan las tareas
 Las tareas están en una cola y los hilos las ejecutan 
según van llegando.
 Cuando un hilo termina de ejecutar una tarea, 
selecciona la siguiente tarea de la cola. Si no hay, 
se bloquea a la espera de una nueva
 Siguen un esquema productor / consumidor
EJECUCIÓN DE TAREAS EN HILOS
27
Ejecución de tareas con grupos de hilos
• Grupos de hilos (Thread Pools)
 Reutilizar un hilo reduce el tiempo de creación y los 
recursos de CPU asociados a costa de algo de 
memoria
 Se inicia la ejecución de las tareas más rápido 
(responsiveness)
 Es una forma de limitar el número máximo de hilos 
ejecutándose a la vez y reduce el riesgo de 
sobrecarga
EJECUCIÓN DE TAREAS EN HILOS
28
Ejecución de tareas con grupos de hilos
• Grupos de hilos (Thread Pools)
EJECUCIÓN DE TAREAS EN HILOS
Executor executor = Executors.newFixedThreadPool(10);
Runnable tarea = ...;
executor.execute(tarea);
Conjunto fijo de hilos. Si no hay 
hilo disponible, la tarea se 
encola y se espera hasta que 
haya un hilo disponible
29
Ejecución de tareas con grupos de hilos
● Grupos de hilos (Thread Pools) 
(métodos estáticos de la clase Executors)
● newFixedThreadPool: Inicialmente crea un conjunto de hilos fijo 
para ejecutar las tareas. Si no hay hilo disponible, la tarea se encola.
● newCachedThreadPool: Cada tarea se ejecuta en un hilo. Si no 
existe ninguno libre, se crea uno. Un hilo permanece libre durante 
cierto tiempo, si no se usa, se elimina.
● newSingleThreadExecutor: Un único hilo de ejecución para las 
tareas (las tareas se ejecutarán de forma secuencial)
● newScheduledThreadPool: Ejecuta tareas a intervalos regulares de 
tiempo (p.e.: Ejecutar esta tarea cada 2 segundos).
EJECUCIÓN DE TAREAS EN HILOS
30
Ejecución de tareas con grupos de hilos
• Ciclo de vida de los executors
 Los executors deben finalizarse de forma adecuada 
(para que la aplicación pueda finalizar su ejecución)
 Como las tareas se ejecutan de forma asíncrona, en 
un momento dado las tareas pueden estar:
 En ejecución
 A la espera
 Ejecutadas
EJECUCIÓN DE TAREAS EN HILOS
31
Ejecución de tareas con grupos de hilos
• Ciclo de vida de los executors
 Se puede finalizar un executor con más o menos 
“delicadeza”.
 Hay diferentes opciones entre los extremos:
 Shutdown: Ejecutar las tareas enviadas pero no 
aceptar nuevas tareas.
 Shutdown now: Finalizar (interrumpir) todas las tareas 
en ejecución y descartar las tareas previamente 
enviadas.
EJECUCIÓN DE TAREAS EN HILOS
32
Ejecución de tareas con grupos de hilos
• Ciclo de vida de los executors
 Se controla mediante el interfaz ExecutorService
EJECUCIÓN DE TAREAS EN HILOS
public interface ExecutorService extends Executor {
 
 void shutdown(); 
 List<Runnable> shutdownNow(); 
 boolean isShutdown(); 
 boolean isTerminated(); 
 boolean awaitTermination(long timeout, TimeUnit 
unit) 
 throws InterruptedException; 
 // Otros métodos... 
}
Terminación ordenada
Terminación forzada
33
Ejecución de tareas con grupos de hilos
• Ciclo de vida de los executors
 Se controla mediante
el interfaz ExecutorService
EJECUCIÓN DE TAREAS EN HILOS
public interface ExecutorService extends Executor {
 
 void shutdown(); 
 List<Runnable> shutdownNow(); 
 boolean isShutdown(); 
 boolean isTerminated(); 
 boolean awaitTermination(long timeout, TimeUnit 
unit) 
 throws InterruptedException; 
 // Otros métodos... 
}
En proceso de terminación?
34
Ejecución de tareas con grupos de hilos
• Ciclo de vida de los executors
 Se controla mediante el interfaz ExecutorService
EJECUCIÓN DE TAREAS EN HILOS
public interface ExecutorService extends Executor {
 
 void shutdown(); 
 List<Runnable> shutdownNow(); 
 boolean isShutdown(); 
 boolean isTerminated(); 
 boolean awaitTermination(long timeout, TimeUnit 
unit) 
 throws InterruptedException; 
 // Otros métodos... 
}
Terminado?
35
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
36
Ejecución asíncrona de tareas
• Hasta ahora hemos visto que un código envía 
una tarea para su ejecución y se olvida de ella
• Hay veces que se quiere tener un mayor control 
de la tarea que se ha enviado al executor:
 Esperar a que termine (o hasta un timeout)
 Obtener un valor obtenido por la tarea
 Gestionar una excepción producida en la tarea
EJECUCIÓN DE TAREAS EN HILOS
37
Ejecución asíncrona de tareas
• Si se quiere tener un mayor control sobre las 
tareas deben implementan Callable<V> (en 
vez de Runnable)
• Los executors tienen el método
submit(Callable<V> task)
EJECUCIÓN DE TAREAS EN HILOS
public interface Callable<V> { 
 V call() throws Exception; 
}
38
Ejecución asíncrona de tareas
• Además, el método submit(Callable<V> c) devuelve 
un objeto de la clase Future<V> para controlar el 
estado de la tarea
EJECUCIÓN DE TAREAS EN HILOS
Callable<String> tarea = new DescargaTweets();
Future<String> f = executor.submit(tarea); 
try {
 String tweets = f.get();
 // Procesar los tweets...
} catch (ExecutionException e) {
 Throwable cause = e.getCause(); 
 // Tratar la excepción producida en la tarea
}
39
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
public interface Future<V> { 
 V get() throws InterruptedException, 
 ExecutionException, CancellationException; 
 
 V get(long timeout, TimeUnit unit) throws 
 InterruptedException, ExecutionException, 
 CancellationException, TimeoutException; 
 boolean cancel(boolean mayInterruptIfRunning); 
 boolean isCancelled(); 
 boolean isDone(); 
} 
Acceder al valor 
obtenido por la 
tarea
40
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
public interface Future<V> { 
 V get() throws InterruptedException, 
 ExecutionException, CancellationException; 
 
 V get(long timeout, TimeUnit unit) throws 
 InterruptedException, ExecutionException, 
 CancellationException, TimeoutException; 
 boolean cancel(boolean mayInterruptIfRunning); 
 boolean isCancelled(); 
 boolean isDone(); 
} Cancelar la tarea (indicando si debe 
interrumpir si se está ejecutando)
41
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
public interface Future<V> { 
 V get() throws InterruptedException, 
 ExecutionException, CancellationException; 
 
 V get(long timeout, TimeUnit unit) throws 
 InterruptedException, ExecutionException, 
 CancellationException, TimeoutException; 
 boolean cancel(boolean mayInterruptIfRunning); 
 boolean isCancelled(); 
 boolean isDone(); 
} Consulta del estado 
de la tarea
42
Ejecución asíncrona de tareas
• Hay veces que controlar a una única tarea no 
es suficiente. Es necesario gestionar un 
conjunto de tareas a la vez
• Por ejemplo, si queremos descargar contenido 
de twitter, facebook, tuenti, instagram a la vez 
y mostrar el contenido según va llegando 
tenemos que gestionar todas las descargas de 
forma conjunta
EJECUCIÓN DE TAREAS EN HILOS
43
Ejecución asíncrona de tareas
• Para gestionar varias tareas a la vez se usa un 
ExecutorCompletionService.
 Se pueden enviar tareas (como cualquier executor)
 Dispone del método take() para obtener el 
Future<V> de las tareas según van completando
 Tiene internamente una cola (queue) con los 
resultados de las tareas creando un esquema 
productor-consumidor
EJECUCIÓN DE TAREAS EN HILOS
44
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
ExecutorService executor = 
 Executors.newFixedThreadPool(10);
CompletionService<String> completionService =
 new ExecutorCompletionService<>(executor);
completionService.submit(new ConsultaTwitterTask());
completionService.submit(new ConsultaFacebookTask());
completionService.submit(new ConsultaInstagramTask());
for (int i = 0, i = 3; i++) {
 try {
 Future<String> f = completionService.take();
 String info = f.get();
 procesarInfo(info);
 } catch (ExecutionException e) {
 throw e.getCause();
 } catch (Exception e){...}
}
Creación de un 
ExecutorCompletionService 
usando un executor creado 
previamente.
45
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
ExecutorService executor = 
 Executors.newFixedThreadPool(10);
CompletionService<String> completionService =
 new ExecutorCompletionService<>(executor);
completionService.submit(new ConsultaTwitterTask());
completionService.submit(new ConsultaFacebookTask());
completionService.submit(new ConsultaInstagramTask());
for (int i = 0, i = 3; i++) {
 try {
 Future<String> f = completionService.take();
 String info = f.get();
 procesarInfo(info);
 } catch (ExecutionException e) {
 throw e.getCause();
 } catch (Exception e){...}
}
Creación de las tareas 
y envío al executor
46
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
ExecutorService executor = 
 Executors.newFixedThreadPool(10);
CompletionService<String> completionService =
 new ExecutorCompletionService<>(executor);
completionService.submit(new ConsultaTwitterTask());
completionService.submit(new ConsultaFacebookTask());
completionService.submit(new ConsultaInstagramTask());
for (int i = 0, i = 3; i++) {
 try {
 Future<String> f = completionService.take();
 String info = f.get();
 procesarInfo(info);
 } catch (ExecutionException e) {
 throw e.getCause();
 } catch (Exception e){...}
}
Obtención del Future<V> que 
permite acceder al valor de la 
tarea que acaba de terminar
47
Ejecución asíncrona de tareas
EJECUCIÓN DE TAREAS EN HILOS
ExecutorService executor = 
 Executors.newFixedThreadPool(10);
CompletionService<String> completionService =
 new ExecutorCompletionService<>(executor);
completionService.submit(new ConsultaTwitterTask());
completionService.submit(new ConsultaFacebookTask());
completionService.submit(new ConsultaInstagramTask());
for (int i = 0, i = 3; i++) {
 try {
 Future<String> f = completionService.take();
 String info = f.get();
 procesarInfo(info);
 } catch (ExecutionException e) {
 throw e.getCause();
 } catch (Exception e){...}
}
Uso del valor
48
Ejecución de tareas en hilos
• Ejecución de tareas
• Ejecución secuencial de tareas
• Ejecución de tareas en un nuevo hilo
• Framework de ejecución de tareas
• Ejecución de tareas con grupos de hilos
• Ejecución asíncrona de tareas
• Conclusiones
PROGRAMACIÓN CONCURRENTE EN JAVA
49
Conclusiones
• En general no es buena idea gestionar los hilos 
directamente, es mejor utilizar un executor.
• En vez de hilos que ejecutan infinitamente, hay 
que diseñar el programa en base a tareas:
 Acotadas en el tiempo
 Independientes entre sí
EJECUCIÓN DE TAREAS EN HILOS
50
Conclusiones
• Habitualmente el pool de hilos (ThreadPool)