TEMA 6: API Java para Concurrencia de Alto Nivel CONTENIDO API Java 5 de Control de la Concurrencia Clases para Gestión de Variables Atómicas Clase java.concurrent.util.concurrent.Semaphore Clase java.concurrent.util.concurrent.CyclicBarrier El API java.concurrent.util.concurrent.locks y la interfaz Condition Colas Contenedoras Sincronizados Pool de Threads: Concepto, utilidad y utilización BIBLIOGRAFÍA RECOMENDADA: [Göe06] Göetz et al. Java Concurrency in Practice, 2006. [Oaks04] Oaks & Wong. Java Threads. O`Reilly, 2004. © Antonio Tomeu API Java para Concurrencia 1 de Alto Nivel Generalidades Disponible a partir de Java 5 Mejoras respecto a los mecanismos previos de control de la concurrencia • • • • Generan menos sobrecarga en tiempo de ejecución Permiten controlar la e.m. y la sincronización a un nivel más fino Soporta primitivas clásicas como los semáforos o las variables de condición en monitores Añade nuevas primitivas y gestión de hilos mediante pools. © Antonio Tomeu API Java para Concurrencia 2 de Alto Nivel ¿Qué paquetes usar? Disponible en los paquetes: java.util.concurrent java.util.concurrent.atomic java.util.concurrent.locks Cierres para control de e.m. Semáforos, Barreras Colecciones (clases contenedoras) concurrentes de acceso sincronizado Variables atómicas © Antonio Tomeu API Java para Concurrencia 3 de Alto Nivel El API java.util.concurrent.atomic Conjunto de clases que soportan programación concurrente segura sobre variables simples tratadas de forma atómica Clases: AtomicBoolean,AtomicInteger, AtomicIntegerArray,AtomicLong,AtomicLong Array,AtomicReference<V>, etc. OJO: No todos los métodos del API de estas clases soportan concurrencia segura. Sólo aquellos en los que se indica que la operación se realiza de forma atómica © Antonio Tomeu API Java para Concurrencia 4 de Alto Nivel import java.util.concurrent.atomic.*; class Hilo extends Thread { AtomicInteger cont = null; //el contador es entero “atómico” public Hilo(AtomicInteger c) {this.cont = c;} public void run() { //se incrementa cont, retornando el valor previo int valor = this.cont.incrementAndGet(); System.out.println(valor); } } public class prueba_var_atomic { public static void main(String[] args) { AtomicInteger cnt = new AtomicInteger(0); for(int i=0; i<100; i++) { Hilo h = new Hilo(cnt); h.start(); } } © Antonio Tomeu } API Java para Concurrencia 5 de Alto Nivel EJERCICIO Escribir una condición de carrera usando objetos AtomicInteger y verificar que se preserva la e.m. © Antonio Tomeu API Java para Concurrencia 6 de Alto Nivel La clase java.util.concurrent.Semaphore Permite disponer de semáforos de conteo de semántica mínima igual a la estándar de Djikstra, o aumentada. Métodos principales: Semaphore (long permits) acquire(), acquire(int permits) release(), release (int permits) tryAcquire(); varias versiones avalaiblePermits() © Antonio Tomeu API Java para Concurrencia 7 de Alto Nivel import java.util.concurrent.*; public class Bloqueo_Semaforo { public static void main(String[] args) throws InterruptedException { Semaphore sem = new Semaphore (2); sem.acquire(2); System.out.println("Semaforo actualizado a valor 0..."); System.out.println("y su estado es: "+sem.toString()); System.out.println("Ahora intentamos adquirirlo..."); sem.tryAcquire(); System.out.println("sin bloqueo por no conseguirlo"); System.out.println("Ahora intentamos adquirirlo..."); sem.tryAcquire(3L, TimeUnit.SECONDS); System.out.println("tras esperar lo indicado sin consguirlo..."); sem.acquire(); System.out.println("Aquí no llegaremos nunca..."); } } © Antonio Tomeu API Java para Concurrencia 8 de Alto Nivel Protocolo de Control de E.M. con Semáforos // Thread A public void run(){ try { s.acquire(); } catch (InterruptedException ex) { } //Bloque de código a ejecutar en e.m s.release(); } // Thread B public void run(){ try { s.acquire(); } catch (InterruptedException ex) { } //Bloque de código a ejecutar en e.m s.release(); } //En el programa principal, hacer siempre Semaphore s = new Semaphore (1); 9 Ejemplo de Control de E.M. con Semáforos import java.util.concurrent.*; public class Tarea_concurrente extends Thread { Semaphore s; //referencia a un semaforo común a otros hilos public Tarea_concurrente(Semaphore param) {s = param;} public void run () { for(;;) { try {s.acquire();} catch (InterruptedException e) {} System.out.println("Hilo "+this.getName()+" entrando a seccion critica"); s.release(); System.out.println("Hilo "+this.getName()+" saliendo de seccion critica"); } } } © Antonio Tomeu API Java para Concurrencia 10 de Alto Nivel EJERCICIO Descargue la clase Tarea_Concurrente.java y compile Descargue la clase Protocolo_em_semaphore.java, compile y ejecute. Diseñe, utilizando semáforos, un código cuyos hilos se interbloqueen:deadlockSem.java © Antonio Tomeu API Java para Concurrencia 11 de Alto Nivel Protocolo de Sincronización Inter-Hilos con Semáforos // Thread A public void esperarSeñal(semaphore s) { try { s.acquire(); } catch (InterruptedException ex) { } } // Thread B public void enviarSeñal(Semaphore s) { s.release(); } //En el programa principal, hacer siempre Semaphore s = new Semaphore (0); © Antonio Tomeu API Java para Concurrencia 12 de Alto Nivel import java.util.concurrent.*; class HiloReceptor extends Thread { Semaphore sem; public HiloReceptor(Semaphore s){sem = s;} public void run() {System.out.println("Hilo Receptor esta esperando la senal"); try{sem.acquire();} catch (InterruptedException e) {} System.out.println("Hilo Receptor ha recibido la senal"); } } class HiloSenalador extends Thread { Semaphore sem; public HiloSenalador(Semaphore s){sem = s;} public void run() {sem.release(); System.out.println("Hilo Senalador enviando senal..."); } } public class protocolo_sincronizacion_semaphore { public static void main(String[] args) { int v_inic_sem = 0; Semaphore s = new Semaphore(v_inic_sem); new HiloReceptor(s).start(); new HiloSenalador (s).start(); } } © Antonio Tomeu API Java para Concurrencia 13 de Alto Nivel EJERCICIO Utilizando las semáforos que sean necesarios, provea la sincronización por turno cíclico de tres hilos diferentes. Nombre sus ficheros HA.java, HB.java,… Implante ahora una lector-escritor con semáforos. Nombre sus ficheros LE1.java, LE2.java,… © Antonio Tomeu API Java para Concurrencia 14 de Alto Nivel La clase java.util.concurrent.CyclicBarrier Una barrera es un punto de espera a partir del cuál todos los hilos se sincronizan Ningún hilo pasa la barrera hasta que todos los hilos esperados llegan a ella Utilidad Unificar resultados parciales Inicio siguiente fase ejecución simultánea © Antonio Tomeu API Java para Concurrencia 15 de Alto Nivel Protocolo de Barrera // Codigo de Thread public Hilo extends Thread { P ublic Hilo (CyclicBarrier bar){…} public void run() { try {int i = bar.await();} catch (BrokenBarrierException e) {} catch (InterruptedException e) {} //codigo a ejecutar cuando se abre barrera… } } } //En el programa principal, hacer siempre int numHilos = n; //numero de hilo que abren barrera CyclicBarrier Barrera = new CyclicBarrier (numHilos); new Hilo(Barrera).start(); © Antonio Tomeu API Java para Concurrencia 16 de Alto Nivel import java.util.concurrent.*; class Hilo extends Thread { CyclicBarrier barrera = null; public Hilo (CyclicBarrier bar) {barrera = bar;} public void run() { try {int i = barrera.await();} catch (BrokenBarrierException e) {} catch (InterruptedException e) {} System.out.println("El hilo "+this.toString()+" paso la barrera..."); } } public class UsaBarreras { public static void main(String[] args) { int numHilos = 3; CyclicBarrier PasoANivel = new CyclicBarrier (numHilos); new Hilo(PasoANivel).start(); } } © Antonio Tomeu API Java para Concurrencia 17 de Alto Nivel EJERCICIO Un vector de 100 enteros es escalado mediante f(v[i])=v[i]2. Posteriomente el vector se suma entero en un único número. Escriba un programa en java multihebrado (utilizando barreras) que realice la tarea: vectSum.java 18 El API java.util.concurrent.locks Proporciona clases para establecer sincronización de hilos alternativas a los bloques de código sincronizado y a los monitores Clases e interfaces de interés: ReentrantLock: proprociona cerrojos de e.m. de semántica equivalente a synchronized, pero con un manejo más sencillo. LockSupport: proporciona primitivas de bloqueo de hilos que permiten al programador diseñar sus clases de sincronización y cerrojos propios Condition:es una interface, cuyas instancias se usan asociadas a locks. Implementan variables de condición y proporcionan una alternativa de sincronización a los métodos wait, notify y notifyAll de la clase Object. © Antonio Tomeu API Java para Concurrencia 19 de Alto Nivel El API de la clase ReentrantLock Proporciona cerrojos reentrantes de semántica equivalente a synchronized. Métodos lock()-unlock() Método isLocked() Método Condition newCondition() que retorna una variable de condición asociada al cerrojo © Antonio Tomeu API Java para Concurrencia 20 de Alto Nivel Protocolo de Control de E.M. con ReentrantLock Definir el recurso compartido donde sea necesario Definir un objeto c de clase ReentrantLock para control de la exclusión mutua. Acotar la sección crítica con el par c.lock() y c.unlock() © Antonio Tomeu API Java para Concurrencia 21 de Alto Nivel Protocolo de Control de E.M. con ReentrantLock class Usuario { private final ReentrantLock cerrojo = new ReentrantLock(); // ... public void metodo() { cerrojo.lock(); try { // ... cuerpo del metodo en e.m. } finally {cerrojo.unlock() } } } NOTA: Semántica equivalente a haber etiquetado el segmento de código crítico como synchronized. Útil para adaptar código concurrente antiguo a java 5. © Antonio Tomeu API Java para Concurrencia 22 de Alto Nivel EJERCICIOS Escribir un protocolo de e.m. con cerrojos ReentrantLock y guardarlo en eMRL.java. Escribir una clase que lo use. Descargue otra vez nuestra vieja clase Cuenta_Banca.java Decida que código debe ser sincronizado, y sincronícelo con cerrojos ReentrantLock. La nueva clase será Cuenta_Banca_RL.java Escriba un programa multihebrado que use objetos de la clase anterior.Llámelo Usa_Cuenta_Banca_Sync.java © Antonio Tomeu API Java para Concurrencia 23 de Alto Nivel La Interfaz Condition Proporciona variables de condición Se usa asociada a un cerrojo: siendo cerrojo una instancia de la clase ReentrantLock, cerrojo.newCondition() retorna la variable. Operaciones: await(), signal(), signalAll() © Antonio Tomeu API Java para Concurrencia 24 de Alto Nivel import java.util.concurrent.locks.*; class buffer_acotado { final Lock cerrojo = new ReentrantLock(); final Condition noLlena = cerrojo.newCondition(); final Condition noVacia = cerrojo.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, cont; //declara y crea un cerrojo //obtiene variables de condicion asociadas a cerrojo //para control de buffer lleno y vacio //array de objetos //punteros de insercion y extracción public void put(Object x) throws InterruptedException { cerrojo.lock(); try { while (cont == items.length) noLlena.await(); //duerme a hilo productor si buffer lleno items[putptr] = x; if (++putptr == items.length) putptr = 0; ++cont; noVacia.signal(); //despierta a hilo consumidor esperando presencia de datos en buffer } finally {cerrojo.unlock();} } public Object take() throws InterruptedException { cerrojo.lock(); try { while (cont == 0) noVacia.await(); //duerme a hilo consumidor si buffer vacio Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --cont; noLlena.signal(); //despierta a hilo productor esperando presencia de huecos en buffer return x; } finally {cerrojo.unlock();} } } © Antonio Tomeu API Java para Concurrencia 25 de Alto Nivel EJERCICIOS Descargue y compile la clase buffer_acotado.java ¿Qué diferencias tiene con la clase Buffer.java? (carpeta código Tema5) Implemente ahora hilos productores y consumidores que usen el buffer acotado y láncelos. Llame a su código prod_con_condition.java © Antonio Tomeu API Java para Concurrencia 26 de Alto Nivel Clases Contenedoras Sincronizadas A partir de Java 5 se incorporan versiones sincronizadas de las principales clases contenedoras Disponibles en java.util.concurrent Clases: ConcurrentHashMap, ConcurrentSkipLis tMap, ConcurrentSkipListSet, CopyOnW riteArrayList, y CopyOnWriteArraySet © Antonio Tomeu API Java para Concurrencia 27 de Alto Nivel Colas Sincronizadas A partir de Java 5 se incorporan diferentes versiones de colas, todas ellas sincronizadas Disponibles en java.util.concurrent Clases: LinkedBlockingQueue, ArrayBlockingQu eue, SynchronousQueue, PriorityBlock ingQueue,y DelayQueue © Antonio Tomeu API Java para Concurrencia 28 de Alto Nivel Caracterísicas Son seguras frente a hilos concurrentes Proporcionan notificación a hilos según cambia su contenido Son autosincronizadas. Pueden proveer sincronización (además de e.m.) por sí mismas Facilitan enormemente la programación de patrones de concurrencia como el P-S © Antonio Tomeu API Java para Concurrencia 29 de Alto Nivel import java.util.concurrent.*; public class Productor implements Runnable { LinkedBlockingQueue<Integer> data; Thread hilo; public Productor(LinkedBlockingQueue<Integer> l) { this.data = l; hilo = new Thread (this); hilo.start(); } public void run() { try { for(int x=0;;x++) { data.put(new Integer(x)); //bloqueo de hilo si no hay hueco System.out.println("Insertando "+x); } } catch(InterruptedException e){} } } © Antonio Tomeu API Java para Concurrencia 30 de Alto Nivel import java.util.concurrent.*; public class Consumidor implements Runnable { LinkedBlockingQueue<Integer> data; Thread hilo; public Consumidor(LinkedBlockingQueue<Integer> l) { this.data = l; hilo = new Thread (this); hilo.start(); } public void run() { try { for(;;) //bloquea a hilo consumidor si no hay datos… System.out.println("Extrayendo "+data.take().intValue()); } catch(InterruptedException e){} } } © Antonio Tomeu API Java para Concurrencia 31 de Alto Nivel EJERCICIOS Descargue (subcarpeta colecciones seguras) Productor.java, Consumidor.java y ProdConColaBloqueante.java Ejecute. ¿Cómo explica el comportamiento observado? Modificaciones: Aumente el número de ranuras de la cola Active varios productores y un consumidor Active un productor y varios consumidores © Antonio Tomeu API Java para Concurrencia 32 de Alto Nivel USO CORRECTO DE COLECCIONES Usamos colecciones a través de interfaces (usabilidad del código) Usar colecciones no sincronizadas no mejora mucho el rendimiento Problemas con esquema tipo productorconsumidor, use colas Minimice la sincronización explícita Si con una colección retarda sus algoritmos, distribuya los datos y use varias © Antonio Tomeu API Java para Concurrencia 33 de Alto Nivel POOL DE HILOS (Thread Pools) Hipótesis: Se desea implantar un servidor web. Enfoques posibles: Implantación de hilo simple Implantación multihebrada © Antonio Tomeu API Java para Concurrencia 34 de Alto Nivel PATRÓN DE SERVIDOR WEB DE HILO SIMPLE class ServidorWebHiloUnico { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); //aquí servidor escuchando puerto 80 while (true) { Socket connection = socket.accept(); procesarSolicitud(connection); } } } © Antonio Tomeu API Java para Concurrencia 35 de Alto Nivel PATRÓN DE SERVIDOR WEB MULTIHEBRADO class ServidorWebMultiHebrado { public static void main(String[] args) throws IOException { ServerSocket socket = new ServerSocket(80); //aquí servidor a la escucha en puerto 80 //un hilo por solicitud while (true) { final Socket connection = socket.accept(); Runnable task = new Runnable() { public void run() { procesarSolicitud(connection); } }; new Thread(task).start(); }//while } } © Antonio Tomeu API Java para Concurrencia 36 de Alto Nivel COMPARATIVA Servidor de hilo simple es ineficiente (hay mucha latencia) Servidor multihebrado mejora la situación Procesa múltiples solicitudes concurrentemente Mejora el rendimiento general Pero no está exento de problemas: Consumo de recursos (al crear los hilos) Estabilidad (número de peticiones elevado) Alternativa: Utilizar un pool de hilos y un ejecutor. © Antonio Tomeu API Java para Concurrencia 37 de Alto Nivel CONCEPTO DE THREAD POOL Un pool de hilos es un conjunto de hilos inactivos a la espera de trabajo Si el programa tienen una tarea que ejecutar, la pasa al pool, que asigna un hilo para ejecutarla Una vez acabada, el hilo vuelve a quedar a la espera de trabajo © Antonio Tomeu API Java para Concurrencia 38 de Alto Nivel EL PATRÓN DE THREAD POOL © ©Antonio AntonioTomeu Tomeu El Lenguaje API Java Javapara y la Concurrencia Programación Concurrente Alto Nivel 39 de ¿Por qué usar un THREAD POOL? Crear un hilo es costoso (latencias). Creando un pool, creamos los hilos una vez y los reutilizamos varias. Permite un mejor diseño de programas, delegando la gestión del ciclo de vida de los hilos al pool Permite paralelizar las aplicaciones si se dispone de varios procesadores © Antonio Tomeu API Java para Concurrencia 40 de Alto Nivel La interfaz java.util.concurrent.Executor Un ejecutor es un concepto genérico, modelado por esta interfaz Proporcionan un patrón de diseño para programas multihebrados, modelándolos como una serie de tareas. Abstracción de los hilos; se crea una tarea, y se pasa al ejecutor Java 5 proporciona dos clases de ejecutores (implementan la interfaz): Ejecutor de un Thread Pool Ejecutor de tareas programadas © Antonio Tomeu API Java para Concurrencia 41 de Alto Nivel La interfaz java.util.concurrent.Executor package java.util.concurrent; public interface Executor{ public void execute (Runnable tarea) © Antonio Tomeu API Java para Concurrencia 42 de Alto Nivel La Clase ThreadPoolExecutor Sirve para crear un pool de hilos Implementa a ExecutorService El constructor: public ThreadPoolExecutor (int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue); © Antonio Tomeu API Java para Concurrencia 43 de Alto Nivel public class Tarea implements Runnable { int numTarea; public Tarea(int n) {numTarea = n;} public void run() {for (int i=1; i<100; i++){ System.out.println("Esta es la tarea numero: "+numTarea+ " imprimiendo "+i);} } } © Antonio Tomeu API Java para Concurrencia 44 de Alto Nivel import java.util.concurrent.*; public class pruebaThreadPool { public static void main(String[] args) { int int nTareas = Integer.parseInt(args[0]); tamPool = Integer.parseInt(args[1]); ThreadPoolExecutor miPool = new ThreadPoolExecutor( tamPool, tamPool, 60000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); Tarea[] tareas = new Tarea [nTareas]; for (int i=0; i<nTareas; i++) { tareas[i] = new Tarea(i); miPool.execute(tareas[i]); } miPool.shutdown(); } } © Antonio Tomeu API Java para Concurrencia 45 de Alto Nivel UN SERVIDOR DE SOCKETS CON POOL DE THREADS import java.net.*; import java.io.*; import java.util.concurrent.*; public class SocketWebServerThreadPool implements Runnable { Socket enchufe; public SocketWebServerThreadPool(Socket s) {enchufe = s;} public void run() { try{ BufferedReader entrada = new BufferedReader( new InputStreamReader(enchufe.getInputStream())); String datos = entrada.readLine(); int j; int i = Integer.valueOf(datos).intValue(); for(j=1; j<=20; j++) System.out.println("Una tarea de servicio escribiendo el dato "+i); enchufe.close(); System.out.println("La tarea de servicio cierra su conexion..."); } catch(Exception e) {System.out.println("Error...");} }//run © Antonio Tomeu API Java para Concurrencia 46 de Alto Nivel public static void main (String[] args) { int i; int puerto = 2001; int nTareas = Integer.parseInt(args[0]); int tamPool = Integer.parseInt(args[1]); ThreadPoolExecutor miPool = new ThreadPoolExecutor( tamPool, tamPool, 60000L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); try{ ServerSocket chuff = new ServerSocket(puerto, 3000); while (true){ System.out.println("Esperando solicitud de conexion..."); Socket cable = chuff.accept(); System.out.println("Recibida solicitud de conexion..."); miPool.execute(new SocketWebServerThreadPool(cable)); }//while } catch (Exception e) {System.out.println("Error en sockets...");} }//main } © Antonio Tomeu API Java para Concurrencia 47 de Alto Nivel EJERCICIOS Descargar y compilar Tarea.java y pruebaThreadPool.java Abrir el administrador de tareas Efectuar las siguientes pruebas: java pruebaThreadPool n m Para n=200 y m creciente Implante ahora un protocolo productor-consumidor con un único buffer, y un pool de 8 tareas productoras y 6 consumidoras © Antonio Tomeu API Java para Concurrencia 48 de Alto Nivel