Tópicos Selectos de Programación UNIDAD III.- Programación Concurrente Sincronización – Todo thread tiene una prioridad. Un thread hereda su prioridad, cuyo valor está entre 1 y 10 – La prioridad puede modificarse con los métodos: – setPriority() – getPriority() Secciones Críticas – Cada thread tiene su stack privado. Pero los threads comparten el mismo heap – Dos threads diferentes pueden actuar sobre un mismo objeto de manera concurrente – Un método tiene varias operaciones que se ejecutan en secuencia. – Si otro thread llama concurrentemente al método, puede que existan resultados Rafael Rivera López 1 Tópicos Selectos de Programación inesperados. – A estos métodos se les conoce como las secciones críticas de procesamiento. – Para prevenir la ejecución concurrente de secciones críticas de deben sincronizar. – Un thread debe bloquear el monitor antes de entrar al método y liberarlo al finalizar. Ejemplo de cuentas bancarias public class Cuenta { private int saldo; private String nombre; public Cuenta(){ this(100,""); } public Cuenta(int saldo,String nombre){ this.saldo = saldo; this.nombre = nombre; } public String getNombre(){ return nombre; } public int getSaldo(){ return saldo; } public void retiro(int cantidad){ saldo -= cantidad; } } public void deposito(int cantidad){ saldo += cantidad; } public class HiloCuenta implements Runnable{ private Cuenta cuenta; public HiloCuenta(Cuenta cuenta){ this.cuenta = cuenta; } Rafael Rivera López 2 Tópicos Selectos de Programación public void run(){ for(int i=0;i<6;i++){ hacerRetiro(10); if(cuenta.getSaldo()<0){ System.out.println("Cuenta sobregirada"); } } } } – private synchronized void hacerRetiro(int cantidad){ String hilo = Thread.currentThread().getName(); if(cuenta.getSaldo() >= cantidad){ System.out.println(hilo + " procede a realizar un retiro " + cuenta.getSaldo()); try{ Thread.sleep(2); } catch(InterruptedException e){ System.out.println("Error de thread"); } cuenta.retiro(cantidad); System.out.println(hilo + " terminó de realizar un retiro " + cuenta.getSaldo()); } else{ System.out.println("No hay suficiente saldo para que " +hilo+" realice un retiro ("+cuenta.getSaldo()+")"); } } El método hacerRetiro si no se sincroniza hace que el programa tenga un comportamiento no deseado. public class Banco { public static void main(String [] args){ Cuenta cuenta = new Cuenta(); HiloCuenta r = new HiloCuenta(cuenta); Thread uno = new Thread(r); Thread dos = new Thread(r); uno.setName("Pedro"); dos.setName("Luisa"); uno.start(); dos.start(); } } Rafael Rivera López 3 Tópicos Selectos de Programación Ejemplo de acceso al jardin package paneldenumero; import java.awt.*; public class PanelDeNumero extends Canvas{ private int valor = 0; String titulo; Font f1 = new Font("Helvetica",Font.BOLD,36); Font f2 = new Font("Times",Font.ITALIC+Font.BOLD,24); public PanelDeNumero(String titulo) { this(titulo,Color.cyan); } public PanelDeNumero(String titulo,Color c) { this.titulo=titulo; setBackground(c); } public void setColor(Color c){ setBackground(c); repaint(); } public void setValor(int valor){ this.valor = valor; repaint(); } public void paint(Graphics g){ g.setColor(Color.black); // Título g.setFont(f2); FontMetrics fm = g.getFontMetrics(); int w = fm.stringWidth(titulo); int h = fm.getHeight(); int x = (getSize().width - w)/2; int y = h; g.drawString(titulo, x, y); g.drawLine(x,y+3,x+w,y+3); // Display el valor g.setFont(f1); fm = g.getFontMetrics(); Rafael Rivera López 4 Tópicos Selectos de Programación } String s1 = String.valueOf(valor); w = fm.stringWidth(s1); h = fm.getHeight(); x = (getSize().width - w)/2; y = (getSize().height+ h)/2; g.drawString(s1, x, y); } package jardinconhilos; import import import import javax.swing.*; java.awt.event.*; java.awt.*; paneldenumero.*; public class Jardin extends JFrame{ private JButton boton; private Torniquete oeste; private Torniquete este; private Contador contador; private PanelDeNumero displayContador; private PanelDeNumero displayOeste; private PanelDeNumero displayEste; private Checkbox sincronizado; public final static int MAX = 20; public Jardin() { setBackground(Color.lightGray); setSize(400,150); addComponentes(); addEventos(); setVisible(true); } public void addComponentes(){ Panel p0 = new Panel(); Panel p1 = new Panel(); Panel p2 = new Panel(); boton = new JButton("Iniciar"); sincronizado = new Checkbox("Sincronizar"); p0.add(boton); p1.setLayout(new BorderLayout()); p1.add(p0,"Center"); p1.add("East",sincronizado); displayContador = new PanelDeNumero("Boda del Año"); displayOeste = new PanelDeNumero("Monse",Color.green); displayEste = new PanelDeNumero("Juan",Color.green); displayContador.setSize(150,100); Rafael Rivera López 5 Tópicos Selectos de Programación displayOeste.setSize(100,100); displayEste.setSize(100,100); p2.add(displayOeste); p2.add(displayContador); p2.add(displayEste); add(p2,"Center"); add(p1,"South"); } public void addEventos(){ boton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { if (oeste==null && este==null) iniciarConteo(); else if (!oeste.isAlive() && !este.isAlive()) iniciarConteo(); } }); } } private void iniciarConteo() { if (!sincronizado.getState()) contador = new Contador(displayContador); else contador = new ContadorSincronizado(displayContador); oeste = new Torniquete(displayOeste,contador); este = new Torniquete(displayEste,contador); oeste.start(); este.start(); } class Contador { private int valor=0; private PanelDeNumero panel; Contador(PanelDeNumero n){ panel = n; panel.setValor(valor); } public void incrementar() { int temp = valor; Simulador.interrumpir(); valor = temp + 1; panel.setValor(valor); } } class ContadorSincronizado extends Contador { Rafael Rivera López 6 Tópicos Selectos de Programación ContadorSincronizado(PanelDeNumero n){ super(n); } public synchronized void incrementar() { super.incrementar(); } } class Torniquete extends Thread { private PanelDeNumero panel; private Contador personas; Torniquete(PanelDeNumero n,Contador c){ panel = n; personas = c; } } public void run() { try{ panel.setValor(0); for (int i=1;i<=Jardin.MAX;i++){ Thread.sleep(500); panel.setValor(i); personas.incrementar(); } } catch (InterruptedException e) {} } class Simulador { public static void interrumpir() { if (Math.random()<0.5) try{ Thread.sleep(200); } catch(InterruptedException e){ System.out.println("Error"); } } } public class Main { public static void main(String[] args) { new Jardin(); } } Rafael Rivera López 7 Tópicos Selectos de Programación – Cuando se sincroniza un método, el objeto utilizado para invocar al método es bloqueado. Pero cuando un bloque es sincronizado, se debe especificar que objeto se bloquea. public synchronized void doStuff () { System.out.println("synchronized"); } equivale a public void doStuff() { synchronized(this) { System.out.println("synchronized"); } } Deadlock – Cuando se bloquean dos objetos por procesos sincrinizados, estos entran en un estado de espera hasta que se libera el objeto, pero esto nunca ocurre, produciendose un Deadlock o “abrazo mortal” public class HiloTransferencia implements Runnable{ private Cuenta cuentaOrigen; private Cuenta cuentaDestino; public HiloTransferencia(Cuenta cuentaOrigen,Cuenta cuentaDestino){ this.cuentaOrigen = cuentaOrigen; this.cuentaDestino = cuentaDestino; } public void run(){ String hilo = Thread.currentThread().getName(); synchronized(cuentaDestino){ try { System.out.println("Waiting for b to complete..."); cuentaDestino.wait(); } catch (InterruptedException e) { Rafael Rivera López 8 Tópicos Selectos de Programación } System.out.println(hilo+" bloquea cuenta de "+ cuentaOrigen.getNombre()); hacerRetiro(cuentaOrigen,50); if(cuentaOrigen.getSaldo()<0){ System.out.println("Cuenta sobregirada"); } System.out.println(hilo+" inicia el deposito "+"en cuenta de "+ cuentaDestino.getNombre()); hacerDeposito(cuentaDestino,50); System.out.println(hilo+" termina la transferencia"); System.out.println(hilo+" termina el bloqueo de "+ cuentaOrigen.getNombre()); notify(); } } private void hacerRetiro(Cuenta cuenta,int cantidad){ String hilo = Thread.currentThread().getName(); if(cuenta.getSaldo() >= cantidad){ System.out.println(hilo + " procede a realizar un retiro " + cuenta.getSaldo()); try{ Thread.sleep(500); } catch(InterruptedException e){ System.out.println("Error de thread"); } cuenta.retiro(cantidad); System.out.println(hilo + " termino de realizar un retiro " + cuenta.getSaldo(); } else{ System.out.println("No hay suficiente saldo para que " +hilo+" realice un retiro ("+cuenta.getSaldo()+")"); } } private void hacerDeposito(Cuenta cuenta, int cantidad){ String hilo = Thread.currentThread().getName(); synchronized(cuenta){ System.out.println(hilo + " procede a realizar un deposito " + cuenta.getSaldo()); try{ Thread.sleep(500); } catch(InterruptedException e){ System.out.println("Error de thread"); } cuenta.deposito(cantidad); System.out.println(hilo + " termino de realizar un deposito " + cuenta.getSaldo()); } } Rafael Rivera López 9 Tópicos Selectos de Programación } – Los deadlock se podrían evitar usando el modelo de productor consumidor – La sincronización entre threads se puede hacer con un modelo productor-consumidor (como si fuera una línea de producción de una fábrica). – – El método wait() envia el thread a la cola de espera, hasta que el objeto es liberado por otro thread – El método notify() indica que el objeto dejo de ser utilizado por un thread y otro thread en espera lo puede ocupar. Rafael Rivera López 10