TEMA 5: Control de la Concurrencia en Java (API Estándar) CONTENIDO Exclusión Mutua con código synchronized. Exclusión Mutua con métodos synchronized. Protocolos de Control de la Exclusión Mutua. Interbloqueos Sincronización: wait(), notify(),notifyAll() Condiciones de Guarda Protocolos de Sincronización Diseño de Monitores en Java BIBLIOGRAFÍA RECOMENDADA: [Göe06] Göetz et al. Java Concurrency in Practice, 2006. [Oaks04] Oaks & Wong. Java Threads. O`Reilly, 2004. [Well04] Wellings, A, Concurrent and Real Time Programming in Java. John Wiley&Sons ,2004 Sintaxis para Bloques de Código Sincronizado public metodo_x (parametros){ //resto codigo del metodo; no se ejecuta en em. /*comienzo sección crítica*/ synchronized (object){ Bloque de sentencias críticas //Todo el código situado entre llaves se ejecuta bajo e.m. } /*fin sección crítica*/ }//metodo_x © Antonio Tomeu 1 EXCLUSIÓN MUTUA ENTRE HILOS § En Java la exclusión mutua se logra a nivel de objetos. Todo objeto tiene asociado un cerrojo (lock). § Técnicas de Control de la E.M. en Java. Semántica de Bloques de Código Sincronizado n Bloques sincronizados. El segmento de código que deba ejecutarse en e.m. se etiqueta como synchronized. Es el equivalente a una región crítica en java. § Métodos sincronizados. Aquellos instancias de recursos que deban manejarse bajo e.m. se encapsulan en una clase y todos sus métodos modificadores son etiquetados con la palabra reservada synchronized. § § Por tanto, dos o más hilos están en e.m. cuando llaman a métodos synchronized de un objeto (de exclusión mutua) o ejecutan métodos que tienen bloques de código sincronizados © Antonio Tomeu Control de la Concurrencia en Java: 2 API Estándar HILOS EN ESPERA n n n Son una implementación del concepto de región crítica. Un bloque de código está sincronizado respecto de un objeto (puede ser el mismo objeto, this) El bloque sólo se ejecuta si se obtiene el cerrojo sobre el objeto. Útil para adecuar código secuencial a un entorno concurrente © Antonio Tomeu Control de la Concurrencia en Java: 5 API Estándar Política de Acceso de los Hilos en Espera HILOS EN ESPERA (BLOQUEADOS) Control de la Concurrencia en Java: 4 API Estándar (BLOQUEADOS) HILO CON BLOQUEO ADQUIRIDO OBJETO DE EX. MUTUA HILO CON BLOQUEO ADQUIRIDO CERROJO OBJETO DE EX. MUTUA HILO LIBERANDO BLOQUEO ESPERA POR CERROJO Datos en em. Código Sincronizado CUESTIONES A DETERMINAR: • ¿Cómo establecer el bloqueo a nivel sintáctico? • ¿Cuál es la semántica del bloqueo? • ¿Qué política de acceso se da a los hilos en espera? © Antonio Tomeu Control de la Concurrencia en Java: 3 API Estándar © Antonio Tomeu Control de la Concurrencia en Java: 6 API Estándar public class Ejemplo_Bloques_Sincronizados { Output sin/con synchronized private int Valor_1; private int Valor_2; protected Object Cerrojo_1 = new Object (); protected Object Cerrojo_2 = new Object (); public int Observa_1 () { synchronized (Cerrojo_1){return (Valor_1); } } public void Modifica_1 (int dato) {synchronized (Cerrojo_1){ Valor_1 = dato; } } public void Modifica_2 (int dato) { synchronized (Cerrojo_2){ Valor_2 = dato; } } public int Observa_2 () { synchronized (Cerrojo_2){ return (Valor_2); } } public Ejemplo_Bloques_Sincronizados() { synchronized (Cerrojo_1){ synchronized (Cerrojo_2){ Valor_1 = Valor_2 = 1; } } } } © Antonio Tomeu Control de la Concurrencia en Java: 7 API Estándar © Antonio Tomeu Pasando el cerrojo como parámetro Acotando la Sección Crítica… public class codBloqueo //usa el cerrojo del propio objeto { private int numVueltas; public codBloqueo(int vueltas){numVueltas = vueltas;} public void metodo() { synchronized(this) //bloqueo para acceso a seccion critica { for(int i=1; i<=numVueltas; i++) System.out.print(i+" "); } } } © Antonio Tomeu Control de la Concurrencia en Java: 8 API Estándar public class MuestraBloqueo implements Runnable { private Object o; public MuestraBloqueo(Object p) //cerrojo como parámetro del constructor {o = p;} public void run() { synchronized(o)//primer hilo que llega adquiere cerrojo { for(int i=1;i<100;i++){ //no habra entrelazado de impresiones System.out.println("Iteracion "+i+" del hilo "+this.toString()); for(int j=1;j<100;j++); //retraso para visualizar } } } public static void main(String[] args) { Object lock = new Object(); //objeto para cerrojo compartido por los hilos Thread h1 = new Thread(new MuestraBloqueo(lock), "hilo 1"); Thread h2 = new Thread(new MuestraBloqueo(lock), "hilo 2"); h1.setPriority(Thread.MIN_PRIORITY); h2.setPriority(Thread.MAX_PRIORITY); h1.start(); h2.start(); } } © Antonio Tomeu public UsacodBloqueo(codBloqueo l) {cerrojo = l;} public void run() { cerrojo.metodo(); //llamada a metodo que tiene codigo sincronizado } public static void main(String[] args) { codBloqueo aux = new codBloqueo(200); UsacodBloqueo h1 = new UsacodBloqueo(aux); UsacodBloqueo h2 = new UsacodBloqueo(aux); h2.start(); h1.start(); } Control de la Concurrencia en Java: 11 API Estándar EJERCICIOS … y usándola desde hilos en modo seguro public class UsacodBloqueo extends Thread { codBloqueo cerrojo; //referencia a objeto compartido Control de la Concurrencia en Java: 10 API Estándar n n n Descargue otra vez nuestra vieja clase Cuenta_Banca.java Decida que código debe ser sincronizado, y sincronícelo. La nueva clase será Cuenta_Banca_Sync.ja va Escriba un programa multihebrado que use objetos de la clase anterior.Llámelo UsaCuenta_Banca_Sync .java } © Antonio Tomeu Control de la Concurrencia en Java: 9 API Estándar © Antonio Tomeu Control de la Concurrencia en Java: 12 API Estándar Sintaxis para Métodos Sincronizados Control con Métodos Sincronizados public class MuestraBloqueoObjeto { public synchronized void metodoA() { for(int i=1;i<100;i++){ System.out.println("Iteracion "+i+" del metodo A "); for(int j=1;j<100;j++);//retraso para visualizar } System.out.println("metodo A liberando cerrojo..."); } public synchronized metodo_x (parametros) { //Bloque de sentencias del método //Todo el código del método se ejecuta en em. }//metodo_x public synchronized void metodoB() { for(int i=1;i<100;i++){ System.out.println("Iteracion "+i+" del metodo B "); for(int j=1;j<100;j++); } System.out.println("metodo B liberando cerrojo..."); } } © Antonio Tomeu Control de la Concurrencia en Java: 13 API Estándar Semántica para Métodos Sincronizados n n n n Un método synchronized fuerza la e.m entre todos los hilos que lo invocan También fuerza la e.m. con otros métodos synchronized del objeto Cualquier hilo que trate de ejecutar un método sincronizado deberá esperar si otro método sincronizado ya está en ejecución Los métodos no sincronizados pueden seguir ejecutándose concurrentemente © Antonio Tomeu Control de la Concurrencia en Java: 16 API Estándar public class UsaMuestraBloqueoObjeto implements Runnable { private MuestraBloqueoObjeto p; //referencia objeto compartido private int caso; public UsaMuestraBloqueoObjeto(MuestraBloqueoObjeto o, int val) {p=o; caso=val;} public void run() { switch(caso){ case 0: p.metodoA(); break; case 1: p.metodoB(); break; } } public static void main(String[] args) { MuestraBloqueoObjeto monitor = new MuestraBloqueoObjeto(); Thread h1 = new Thread(new UsaMuestraBloqueoObjeto(monitor,0)); Thread h2 = new Thread(new UsaMuestraBloqueoObjeto(monitor,1)); h1.start(); h2.start(); } } © Antonio Tomeu Control de la Concurrencia en Java: 14 API Estándar Además… El cerrojo está asociado a cada instancia de una clase (objeto) n Los métodos de clase (static) también pueden ser synchronized. n En clases heredadas, los métodos sobreescritos pueden ser sincronizados o no, sin afectar a cómo era (y es) el método de la superclase. n © Antonio Tomeu Control de la Concurrencia en Java: 17 API Estándar /*Ejemplo de Exclusion Mutua entre Hilos *@author Antonio J. Tomeu *Varios hilos concurrentes modifican contador protegido bajo e.m. *El metodo que incrementa al contador es synchronized *Crea un array de hilos que incrementan bajo e.m. el contador *Sintaxis de uso: java ExMutua n m donde: *n es el numero de hilos concurrentes *m es el valor inicial del contador */ class ObCritico { private int Dato; //Contiene el objeto critico public ObCritico (int VInicial) //el constructor {Dato = VInicial;} public synchronized void Incremento() //e ejecutar bajo e.m. {Dato++;} public int Valor () //hace una lectura. No necesita e.m. {return (Dato);} } © Antonio Tomeu Control de la Concurrencia en Java: 15 API Estándar © Antonio Tomeu Control de la Concurrencia en Java: 18 API Estándar public class ExMutua extends Thread { private ObCritico SC; public ExMutua (ObCritico SecCritica) {SC = SecCritica;} public void run() { for(;;) { SC.Incremento(); System.out.println("El hilo con id. "+getName()+" ha ajustado el objeto a valor "+SC.Valor()); } } public static void main(String[] args) { if (args.length !=2) { System.err.println ("Sintaxis: java ExMutua n m"); System.exit (1); } int NumHilos = Integer.valueOf(args[0]).intValue(); //num. de hilos ObCritico ContadorCritico = new ObCritico (Integer.valueOf(args[1]).intValue()); © Antonio Tomeu Control de la Concurrencia en Java: 19 API Estándar Protocolo 1 de Control de E.M. en Java n n n Acceder al recurso compartido donde sea necesario Definir un objeto para control de la exclusión mutua (o usar el propio objeto, this). Sincronizar el código crítico utilizando el cerrojo del objeto de control dentro de un bloque synchronized de instrucciones © Antonio Tomeu Control de la Concurrencia en Java: 22 API Estándar Protocolo 2 de Control de E.M. en Java ExMutua [] Hilos = new ExMutua [NumHilos]; for(int i=0; i<=NumHilos-1; i++) {Hilos[i] = new ExMutua (ContadorCritico);} for(int i=0; i<=NumHilos-1; i++) {Hilos[i].start();} } } n n n n n © Antonio Tomeu Control de la Concurrencia en Java: 20 API Estándar Encapsular el recurso crítico en una clase. Definir todos los métodos de la clase como no estáticos y synchronized O al menos, todos los modificadores Crear hilos que compartan una instancia de la clase creada !OJO¡ Esto no provee sincronización © Antonio Tomeu EJERCICIOS n n n Descargue otra vez nuestra vieja clase Cuenta_Banca.java Decida que métodos deben ser sincronizados, y sincronícelos. La nueva clase será Cuenta_Banca_Sync_Met.java Escriba un programa multihebrado que use objetos de la clase anterior.Llámelo Usa_Cuenta_Banca_Sync_Met.java © Antonio Tomeu Control de la Concurrencia en Java: 21 API Estándar Control de la Concurrencia en Java: 23 API Estándar EJERCICIOS n n n Modele con una clase alguna situación donde haya presencia de concurrencia y condiciones de concurso. Guárdela en Recurso.java Modifique la clase anterior protegiendo al recurso mediante el Protocolo 2 de Control de la E.M. Llame a la clase Recurso_em.java. Escriba ahora un programa llamado Prueba_Recurso_em.java que haga uso de la clase anterior. Debe crear varios hilos concurrentes mediante implementación de la interfaz Runnable. © Antonio Tomeu Control de la Concurrencia en Java: 24 API Estándar SINCRONIZACIÓN ENTRE HILOS La Posesión del Bloqueo es por Hilo… n n n n n Un método sincronizado puede invocar a otro método sincronizado sobre el mismo objeto (REENTRANCIA) Permite llamadas recursivas a métodos sincronizados Permite invocar método herededados sincronizados Descargue obj_protected.java y usa_bj_protected.java. Pruébelos: ¿a qué conclusión llega? Incorpore ahora un método m3() recursivo dentro de obj_protected. java . ¿Se mantiene su conclusión? © Antonio Tomeu § En Java la sincronización entre hilos se logra con los métodos wait, notify y notifyAll (Clase Object) § Deben ser utilizados únicamente dentro de métodos o código de tipo synchronized. § Cuando un hilo llama a un método que hace wait las siguientes acciones son ejecutadas atómicamente: 1. Hilo llamante suspendido y bloqueado. 2. Exclusión mutua sobre el objeto liberada. 3. Hilo colocado en una cola única (el wait-set) de espera asociada al objeto. § Cuando un hilo llama a un método que hace notify, uno de los hilos bloqueados en la cola pasa a listo. Java no especifica cuál. Depende de la implementación (JVM)Si se llama al método notifyAll todos los hilos de dicha cola son desbloqueados y pasan a listos. Accederá al objeto aquél que sea planificado. Si el wait-set está vacío, notify y notifyAll no tienen efecto. § Control de la Concurrencia en Java: 25 API Estándar © Antonio Tomeu Control de la Concurrencia en Java: 28 API Estándar INTERBLOQUEO (DEADLOCK) ENTRE HILOS (1/2) Se producen cuando hay condiciones de espera de liberación de bloqueos cruzadas entre dos hilos n Sólo pueden evitarse mediante un análisis cuidadoso y riguroso del uso de código sincronizado n © Antonio Tomeu Control de la Concurrencia en Java: 26 API Estándar INTERBLOQUEO (DEADLOCK) ENTRE HILOS (2/2) PATRÓN ESTÁNDAR public class Deadlock { public static void main(String[] args) { final Object region_A = new Object(); final Object region_B = new Object(); Thread Hilo_A = new Thread(new Runnable(){ public void run() { synchronized(region_A) { synchronized(region_B) { calculo(); } } } }); Thread Hilo_B = new Thread(new Runnable(){ public void run() { synchronized(region_B) { synchronized(region_A) { calculo(); } } } }); public class aDormir extends Thread { public aDormir() {} public void run() { System.out.println("El hilo "+this.getName()+" dijo: mi vida activa fue breve..."); synchronized(this) { try{wait();}catch (InterruptedException e){} //cada hilo dormido sobre su propio cerrojo System.out.println(this.getName()+" dijo: pero he revivido..."); } } public void despertar(){synchronized (this){notify();}} public static void main(String[] args) { aDormir [] h = new aDormir[10]; for(int i=0; i<10;i++) {h[i]=new aDormir(); h[i].start();} h[5].despertarUno(); Hilo_B.start(); Hilo_A.start(); } } } © Antonio Tomeu Control de la Concurrencia en Java: 27 API Estándar } 30 Protocolo de Sicronización Inter-Hilos en Java Con Espera Ocupada public class aDormir2 extends Thread { Object lock; public aDormir2(Object l) {lock=l;} public void run() { System.out.println("El hilo "+this.getName()+" dijo: mi vida activa fue breve..."); synchronized(lock) { try{lock.wait();}catch (InterruptedException e){} //hilos dormidos sobre un cerrojo común System.out.println(this.getName()+" dijo: pero he revivido..."); } } // Thread A public void waitForMessage() { while (hasMessage == false) { Thread.sleep(100); } } PRUEBE AHORA aDormir3.java public void despertar(){synchronized (lock){lock.notify();}} public void despertarTodos(){synchronized (lock){lock.notifyAll();}} // Thread B public void setMessage(String message) { ... hasMessage = true; public static void main(String[] args) { Object cerrojo = new Object(); aDormir2 [] h = new aDormir2[10]; for(int i=0; i<10;i++) {h[i]=new aDormir2(cerrojo); h[i].start();} h[5].despertar(); h[5].despertarTodos(); System.out.print("Todos terminaron..."); //¿observa algo anómalo? © Antonio Tomeu 31 Control de la Concurrencia en Java: 34 API Estándar }} Protocolo de Sicronización Inter-Hilos en Java con sincronización wait-notify Condiciones de Guarda § No se puede escribir código de hilos asumiendo que un hilo concreto recibirá la notificación. § Diferentes hilos pueden estar bloqueados sobre un mismo objeto a la espera de diferentes condiciones § Dado que notifyAll() los despierta a todos de forma incondicional, es posible que reactive hilo para los cuales no se cumple aún la condición de espera. Solución: siempre que el hilo usuario invoca a wait(), lo primero al despertar es volver a comprobar su condición particular, volviendo al bloqueo si ésta aún no se cumple. § © Antonio Tomeu // Thread B public synchronized void setMessage(String message) { ... notify(); OJO: No es perfecto. La señal puede perderse (y se pierde) si nadie está esperándola. SOLUCIÓN: Utilizar condiciones. A sólo se bloqueará si al comprobar una condición ésta es falsa, y B se asegurará de hacer que la condición sea verdadera antes de notificar. Control de la Concurrencia en Java: 32 API Estándar Patrón de Código para Condiciones de Guarda public synchronized void m_receptor_señal(){ //hilo usuario se bloqueará en el metodo a espera de condición //el hilo usuario pasa al wait-set while (!condicion) try{wait();} //condición de guarda catch ()(Exception e){} //codigo accedido en e.m. } -----------------------------------------------------------public synchronized void m_emisor_señal(){ //hilo usuario enviará una señal a todos los hilos del waitset //la guarda garantiza que se despiertan los adecuados //codigo en e.m. que cambia las condiciones de estado notiyAll(); } © Antonio Tomeu // Thread A public synchronized void waitForMessage() { try { wait(); } catch (InterruptedException ex) { } } Control de la Concurrencia en Java: 33 API Estándar © Antonio Tomeu class Sincro { Control de la Concurrencia en Java: 35 API Estándar Patrón “sincronizador” int Turno; public Sincro(int t) {Turno = t;} public synchronized void metodo1() { while(Turno != 1) try {wait();} catch (Exception e){} System.out.println(" Turno al hilo1..."); Turno=2; notifyAll(); } public synchronized void metodo2() { while(Turno != 2) try {wait();} catch (Exception e){} System.out.println("Turno al hilo 2..."); Turno = 3; notifyAll(); } © Antonio Tomeu Control de la Concurrencia en Java: 36 API Estándar public synchronized void metodo3() { while(Turno != 3) try {wait();} catch (Exception e){} System.out.println("Turno al hilo 3..."); Turno = 1; notifyAll(); } }//Sincro class Hilo1 { Sincro public public { extends Thread ref; Hilo1(Sincro obj){ref=obj;} void run() for(;;){ref.metodo1();} } }//Hilo1 © Antonio Tomeu Control de la Concurrencia en Java: 37 API Estándar class Hilo2 extends Thread { Sincro ref; public Hilo2(Sincro obj){ref=obj;} public void run() { for(;;){ref.metodo2();} } }//Hilo2 class Hilo3 { Sincro public public { extends Thread ref; Hilo3(Sincro obj){ref=obj;} void run() for(;;){ref.metodo3();} } }//Hilo3 © Antonio Tomeu Control de la Concurrencia en Java: 38 API Estándar public class Sincronizacion { public static void main (String [] args) { Sincro m = new Sincro(2); new Hilo1(m).start(); new Hilo2(m).start(); new Hilo3(m).start(); System.out.println("hilos lanzados..."); }//main }//Sincronizacion © Antonio Tomeu Control de la Concurrencia en Java: 39 API Estándar EJERCICIO Utilizando condiciones de guarda, modele un protocolo de sincronización simple Hilo B -> Hilo A que sea completamente correcto. n Guarde su código en ficheros S1.java, S2.java… n © Antonio Tomeu Control de la Concurrencia en Java: 40 API Estándar