Hilos
Hilos
Hilos
h2 = new Hilo1("Dos"); h1.start(); h2.start(); } } class Hilo1 extends Thread{ String s; public Hilo1(String s){ this.s = s; } public void run(){ for(int i=0; i<10; i++){ System.out.println(s+" "+i); try{ sleep(Math.round(Math.random()*1000)); }catch(InterruptedException e){} } }
En este programa, tenemos definidas dos clases, la clase Hilos1 que es la clase principal, la cual va a crear en este caso dos hilos que arrancarn su ejecucin y la clase Hilo1 que es donde programaremos el hilo a ejecutar, como puedes ver la ejecucin de la clase Hilo1 extiende a Thread, como extiende a Thread, automticamente hereda el mtodo run pero como sabemos el mtodo run no hace nada, ah es donde nosotros programamos lo que queremos que se ejecute en un hilo.
El constructor de Hilo1 recibe como parmetro un string que nos va a servir para identificar que hilo se est ejecutando. Ahora veamos la programacin de run, es simplemente un ciclo for que mediante una variable de control i, se ejecutar 10 veces; dentro de su ejecucin simplemente va a imprimir el string que le mandamos de parmetro al constructor y despus el valor de la variable i, inmediatamente despus de haber ejecutado la impresin en pantalla, vamos a poner al hilo a dormir, esto lo hacemos con el mtodo sleep, si analizas el parmetro de sleep que tiene que ser un valor de tipo long realmente es un nmero aleatorio entre 0 y 1 multiplicado por 1000, que quiere decir esto? Que el hilo se ir a dormir por una fraccin de un segundo, el valor de esta fraccin ser aleatorio. Algo importante que puedes ver es que sleep est dentro de un bloque try-catch porque as lo requiere la clase Thread o mejor dicho el mtodo sleep de la clase Thread, este mtodo sleep arroja la interrupcin InterruptedException la cual tienes que atrapar cuando utilizas sleep.
Entonces como vemos la clase Hilo1 simplemente ejecutar un ciclo durante 10 veces y dentro de ese ciclo imprimir un letrero y dejar de competir por el CPU por una fraccin de segundo.
Ahora vamos a la clase principal Hilos 1, aqu como puedes ver estamos creando dos ejemplares (objetos) o dos instancias de la clase Hilos1, a la primera le mandamos el parmetro string "Uno"
y a la segunda el parmetro string "Dos", el hecho de crear dos instancias no es todo, adems debemos de arrancar los hilos, y esto se hace mandando ejecutar el mtodo run, pero como ya vimos el mtodo run no se ejecuta directamente, tienes que llamar a start que son las dos ltimas instrucciones del mtodo main, cuando ves h1.sart() y h2.sart() en ese momento comienza la ejecucin de dos hilos, tcnicamente lo que comienza es una competencia por el CPU. En la parte inferior de la filmina puedes ver la salida de una de tantas ejecuciones de este programa, como puedes ver no hay un orden en el cual se ejecutan los hilos, ambos estn compitiendo por el CPU o estn en estado detenidos por la llamada sleep o un tiempo aleatorio.
El mismo ejemplo, pero implementando Runnable public class Hilos2{ public static void main(String[] args){ Hilo1 h1 = new Hilo1("Uno"); Hilo1 h2 = new Hilo1("Dos"); Thread t1 = new Thread(h1); Thread t2 = new Thread(h2); t1.start(); t2.start(); } } class Hilo1 implements Runnable{ String s; public Hilo1(String s){ this.s = s; } public void run(){ for(int i=0; i<10; i++){ System.out.println(s+" "+i); try{ Thread.sleep(Math.round(Math.random()*1000)); }catch(InterruptedException e){} } } }
Ahora veamos el mismo ejemplo pero implementando runnable es decir por alguna circunstancia, nuestra clase Hilo1 no va poder extender a Thread en ese caso necesitamos que implemente runnable y cuando implementamos una interfaz nos comprometemos a programar todos los mtodos de esa interfaz, en este caso la interfaz runnable solamente tiene run.
Si te fijas, la clase Hilo1 es idntica a la clase Hilo1 del ejemplo pasado simplemente hemos sustituido extends por implement runnable, ahora veamos la clase principal que se llama Hilos2, tambin es muy similar a la anterior, el nico detalle es que cuando creamos una instancia o un ejemplar de la clase Hilo1 todava no hemos creado un thread que es el primer paso antes de mandar a ejecutar el mtodo run entonces las dos instrucciones que continan, son las que se encargan de crear instancias de la clase Thread , en el ejemplo pasado eso era automtico porque al crear una instancia de Hilo1 automticamente creabas una instancia de Thread, el otro detalle es que cuando creas una instancia de Thread tienes que asociarle un hilo o una clase que implemente runnable, en este caso los dos objetos, h1 y h2 son los que se asocian a los threads t1 y t2 respectivamente.
Y por ltimo para arrancar los hilos simplemente mandas llamar start en cada uno de los ejemplares empleados anteriormente, y como puedes ver en la parte inferior de la filmina est una salida de la ejecucin de este programa que es similar a la anterior obviamente el orden de ejecucin va a ser diferente por que ambos hilos compiten por recursos y no asegura un orden.
Algo importante del archivo Hilos1.java y el archivo Hilos2.java se encuentran en los archivos auxiliares que bajars de la red (ver ejercicio), es que cuando ejecutes Hilos1 complalo previamente y cuando ejecutes Hilos2 vuelve a compilar los dos, la razn es que ambos archivos tienen definida una clase Hilo1, lo que pasara si no recompilas Hilos2 antes de ejecutarlo y previamente compilaste Hilos1, la clase que se compil es Hilo1 pero que extiende a Thread y no Hilo1 que implementa runnable, esto est hecho en forma intencional para que veas que se generan dos clases en el momento de compilacin a pesar de que es solamente un archivo fuente. As es que por favor antes de ejecutar cada uno de los programas complalos.
Estado de un Hilo Un hilo puede estar en uno de cuatro estados, cuando acabas de crearlo es un hilo nuevo pero
que no est en ejecucin ni est compitiendo por recursos del CPU, el siguiente estado se denomina ejecutable, no en ejecucin, ejecutable indica que el hilo est compitiendo por el CPU contra los otros hilos activos, solamente un hilo estar en ejecucin pero puede haber muchos hilos en estado ejecutable que en cualquier momento pueden tomar posesin del CPU, el tercer estado posible es detenido, ah simplemente el hilo no est compitiendo por el CPU y esto lo podamos ver en el ejemplo pasado cuando ejecutbamos el mtodo sleep y por ltimo el estado muerto, esto sucede cuando el hilo termina de ejecutar el mtodo run.
En la figura puedes ver grficamente los cuatro estados y la manera de irse a cada uno de ellos. El estado nuevo es cuando creas una instancia o un ejemplar de la clase Thread o de alguna clase que extienda a Thread. Para que el hilo que creaste pase a estado ejecutable necesitas llamar al mtodo start y el hilo estar en estado ejecutable que es compitiendo por el CPU, hasta que sucedan algunas cosas, por un lado si mandas llamar al mtodo sleep automticamente se ir al estado de detenido, cuando sale de este estado de detenido es cuando hayan transcurrido la cantidad de milisegundos que especificaste en el mtodo sleep en ese momento el hilo vuelve a ser ejecutable, es decir sigue compitiendo por el CPU con los otros hilos activos, adems si llamas al mtodo wait se ir a estado de detenido y saldr de este si ejecutas notify o notifyAll (esto no lo cubriremos en esta leccin), otra causa es que el hilo haga una operacin de entrada-salida en ese momento se bloquea hasta que se complete la operacin de entrada salida y por ltimo es llamar al mtodo suspend en ese momento el hilo se va a estado de detenido y se queda ah hasta que se haga una llamada al mtodo resume stos dos mtodos estn puestos en cursivas porque estn desaprobados, es decir, no es recomendado su uso, la especificacin tcnica del por qu est fuera de los alcances de este curso pero simplemente es necesario tener en cuenta que la utilizacin de estos dos mtodos est desaprobada y no es conveniente hacerla.
El cuarto estado, cuando nuestro hilo se muere, llegamos a l ejecutando el mtodo run y terminando su ejecucin, la otra causa puede ser que ejecutemos el mtodo stop pero este mtodo tambin est desaprobado as es que no lo utilizaremos, entonces la manera de terminar con nuestros hilos o matarlos es, simplemente que termine la ejecucin del mtodo run en forma natural.
En los dos ejemplos anteriores nuestro mtodo run era simplemente un for que se ejecutaba 10 veces, al terminar la ejecucin del run automticamente el hilo se mora. Y por ltimo, cuando se est en estado ejecutable y se ejecuta el mtodo yield el hilo cede el CPU a algn hilo de igual
prioridad y comienza a competir por el CPU con los otros hilos, entonces el hecho de llamar yield es simplemente dar la posibilidad a que otros hilos de igual prioridad puedan ejecutarse y que un hilo no adquiera el CPU por tiempo indefinido.
new Tread (...) - Creado, pero todava no comienza ejecucin start ( ) - crea los recursos necesarios para su ejecucin, lo deja listo para que la mquina virtual lo ponga a correr llamando al mtodo run (), compite por los recursos del sistema. Bloqueado por I/O, sleep () o wait () - La mquina virtual puede seleccionar otro hilo para ejecucin, para salir de este estado la accin debe de corresponder la accin que la bloque. Muerto - Termin run (), una vez muerto un hilo no puede volver a ejecucin
Resumiendo, tenemos cuatro estados posibles de un hilo, nuevo, recin creado pero no comienza su ejecucin despus de ejecutar start se crean todos los recursos necesarios para hacer la ejecucin, ejecutable comienza a competir por el CPU con otros hilos, tercer estado, detenido llegamos a l cuando el hilo se bloquea con una operacin de entrada salida se invoca al mtodo sleep o el mtodo wait en ese momento la mquina virtual selecciona otro hilo para ejecutarlo y por ltimo el estado muerto que para nosotros simplemente ser el trmino de la ejecucin del mtodo run, es importante saber que una vez que un hilo est muerto no se puede volver a ejecutar, es decir no puede mandar llamar start otra vez. Qu es lo que tienes que hacer?, es crear otra instancia de tu clase para que se cree un nuevo hilo. Ejemplo Veamos un ejemplo un poco ms entretenido, en este caso ser un applet, el archivo fuente es Hilos3.java, tambin tienes un archivo llamado hilos3.html que puedes abrir desde un browser para ver la ejecucin.
Antes de comenzar a hablar de los componentes de este applet y como maneja hilos, veamos como es la ejecucin en pantalla, lo que vers es tres contadores que irn desplegando el valor de cada uno a velocidades diferentes, adems en el rengln inferior ves tres botones con los cuales puedes parar el conteo de cada uno de los contadores que mencionamos, lo importante aqu es que sern tres contadores que se ejecutarn en forma simultnea y contarn a diferente velocidad, entonces podrs hacer una pausa y cargar en el browser la pgina hilos3.html, ver la ejecucin, jugar un momento con el applet y despus volver a la leccin donde continuaremos explicando la programacin de este applet. En la filmina lo primero que hacemos es el desplegado grfico de nuestro applet, tenemos 3 botones cuya etiqueta es para el 1, para el 2 y para el 3 y tenemos tres campos de texto cuyo valor inicial es el valor 0. Adems tenemos tres objetos de la clase Cuenta que es realmente un hilo que es el que ir desplegando a diferentes velocidades cada uno de los contadores. En el mtodo init establecemos el layout a un GridLayout de dos renglones y tres columnas agregamos los 6 componentes grficos y sin olvidar el detalle de hacer que los botones respondan a eventos registrando el escuchador que en este caso es el applet con cada uno de los botones que dispararn los eventos de accin.
import java.awt.*; import java.applet.*; import java.awt.event.*; public class Hilos3 extends Applet implements ActionListener{ Button boton1 = new Button("Para el 1"); Button boton2 = new Button("Para el 2"); Button boton3 = new Button("Para el 3"); TextField cuenta1 = new TextField("0"); TextField cuenta2 = new TextField("0"); TextField cuenta3 = new TextField("0"); Cuenta t1, t2, t3; public void init(){ setLayout(new GridLayout(2,3)); add(cuenta1); add(cuenta2); add(cuenta3); add(boton1); add(boton2); add(boton3); boton1.addActionListener(this); boton2.addActionListener(this); boton3.addActionListener(this); }
Continuando con nuestro applet vemos que en el mtodo start, este mtodo start es del applet, no tiene absolutamente nada que ver con la llamada start de un hilo, en el mtodo start creamos los tres hilos, t1, t2 y t3 que son de la clase Cuenta, que son como puedes ver el constructor de Cuenta, requiere dos parmetros, el primero es la cantidad de milisegundos entre cada conteo, t1 dejar pasar medio segundo t2 un segundo completo y t3, dos segundos.
El segundo parmetro es el campo de texto donde se ir desplegando el conteo cada hilo tendr su propio campo de texto, en la parte final de start tenemos que arrancar los hilos correspondientes, ya los creamos pero como sabemos eso no es suficiente, tenemos que arrancarlos.
En el mtodo stop lo que hacemos es paramos el conteo, es decir matamos al hilo, normalmente esto lo hacemos con una variable de control booleana como vers en la siguiente filmina, nuestro mtodo run ser controlado por esta variable booleana en el momento que sea falsa, se terminar la ejecucin del ciclo y por lo tanto se terminar la ejecucin del run y el hilo morir. Y por ltimo simplemente identificamos en el mtodo actionPerformed que botn ser presionado y simplemente hacemos la variable contando del hilo correspondiente igual a falso para matar el hilo, entonces
como puedes ver aqu lo importante es la creacin de los tres hilos los dos parmetros que se manda a cada uno y cmo podemos parar la ejecucin de los hilos. Ahora veamos como est programada la clase Cuenta y si es realmente la clase que permite ejecutar esto en forma simultnea, cada uno de los tres contadores. public void start(){ t1 = new Cuenta(500,cuenta1); t2 = new Cuenta(1000,cuenta2); t3 = new Cuenta(2000,cuenta3); t1.start(); t2.start(); t3.start(); } public void stop(){ t1.contando = false; t2.contando = false; t3.contando = false; } public void actionPerformed(ActionEvent e){ if (e.getSource().equals(boton1)) t1.contando = false; else if (e.getSource().equals(boton2)) t2.contando = false; else if (e.getSource().equals(boton3)) t3.contando = false; }
Nuestra clase Cuenta obviamente extiende a Thread, tenemos algunas variables de la clase por un lado tenemos un deltaT que es de tipo long, que es el que determinar la velocidad del conteo por otra parte tenemos una variable entera que es el nmero que llevar el conteo en este caso se llama cuenta y es inicializada al valor de 0. Adems tenemos una variable caja que ser un campo de texto, que no va a existir dentro de esta clase pero esa es la variable que va a contener una referencia al campo de texto correspondiente en el applet y por ltimo una variable booleana contando que me controlar hasta donde vive el hilo. Despus puedes ver el constructor de la clase Cuenta que recibe dos parmetros que ya mencionamos y que los guardamos en las variables de la clase Cuenta deltaT y caja. Esto es importante, porque necesitamos una referencia al campo de texto correspondiente en el applet para desplegar el conteo desde un objeto de la clase Cuenta sino, no podramos tener acceso a los campos de texto del applet, necesitamos una referencia y esa referencia es la que se manda como parmetro. Y por ltimo el mtodo run, que es el corazn de nuestro hilo y de todos los hilos simplemente es un ciclo while mientras contando sea verdadera se ir ejecutando, se incrementa la variable cuenta en uno y despus se despliega su valor en el campo de texto correspondiente, acto seguido simplemente se manda llamar sleep por un deltaT para parar el conteo y ese es todo nuestro ciclo. En el momento en que se sale del ciclo en el campo de texto correspondiente se despliega el letrero: ya se muri, indicando que el hilo ya est muerto, este es todo nuestro applet y si ya viste la ejecucin puedes ver que los tres contadores, cuentan a diferente velocidad adems lo importante es que los tres contadores son exactamente de la misma clase, simplemente las creas con diferente parmetro, entonces en el momento de ejecutar este applet los que tu tienes es cuatro hilos corriendo, el hilo donde corre el applet que es donde se despliegan los contadores y el que controla los eventos de los botones y cada uno de los tres hilos que ejecuta el conteo y actualiza su correspondiente campo de texto. Espero que con esto quede claro el concepto de hilos y que veas que ventaja reporta. Este programa sera prcticamente imposible realizarlo si es que no tuviramos el concepto de hilos.
class Cuenta extends Thread{ long deltaT; int cuenta=0; TextField caja; boolean contando = true; public Cuenta(long deltaT, TextField caja){ this.deltaT = deltaT; this.caja = caja; } public void run(){ while(contando){ cuenta ++; caja.setText(Intejer.toString(cuenta)); try{ sleep(deltaT);
} }
} caja.setText("Ya se muri); }
Prioridades Un punto importante a conocer acerca de los hilos es el manejo de prioridades y cmo la mquina virtual selecciona a un determinado hilo para su ejecucin.
Los hilos tienen prioridades, esta prioridad es relativa a otros hilos en Java. Cuando se dice que la prioridad es relativa se quiere decir que no se sabe exactamente el mapeo a las prioridades nativas de un determinado sistema operativo, lo nico con lo que se cuenta es que las prioridades entre los hilos es relativa a otros hilos dentro de Java.
La mquina virtual simplemente selecciona al hilo de mayor prioridad que est en estado ejecutable y lo pone en ejecucin, si es que existen dos o ms hilos de igual prioridad, se selecciona uno en forma aleatoria, el hilo permanece en ejecucin hasta que llama al mtodo yield o pasa a estado no ejecutable o a estado detenido por algunas de las causas vistas anteriormente.
Otra cosa importante es que un hilo hereda automticamente su prioridad del hilo que lo crea, en
el ejemplo anterior los tres hilos tienen la misma prioridad y esta es igual a la prioridad del applet.
Se puede modificar la prioridad de un hilo con una llamada al mtodo setPriority que recibe como parmetro un entero, en estos momentos Java tiene definido 10 prioridades de 1 a 10, no es conveniente utilizarlas as en forma absoluta porque no se sabe que es lo que va a pasar posteriormente con esto de las prioridades, por lo pronto se pueden utilizar tres constantes que son, prioridad mnima, prioridad normal y prioridad mxima. Normalmente lo que se hace cuando se tiene una animacin por ejemplo, es establecer la prioridad de esa animacin a la prioridad mnima utilizando el mtodo setPriority y mandndole como parmetro la constante MIN_PRIORITY de la clase Thread.
Si un hilo de alta prioridad se pone en ejecucin y no tiene absolutamente ninguna operacin de entrada-salida este hilo puede acaparar todo el CPU si es que no llama en algn momento al mtodo yield o al mtodo sleep, esto se conocen como hilos egostas, as que es conveniente que cuando los hilos no tengan operaciones de entrada-salida ejecuten peridicamente yield o sleep para que no haya ningn acaparamiento del CPU por parte de un hilo egosta.
Es conveniente saber que yield slo cede el CPU a hilos de igual prioridad, sino existen hilos de igual prioridad al que se est ejecutando la llamada de yield es como si no existiera.
Resumiendo lo anterior se puede decir que: Todos los hilos tienen una prioridad relativa a otros hilos. La mquina virtual selecciona al hilo de mayor prioridad en estado ejecutable y lo pone a correr. El hilo que est en ejecucin hasta que: llama a yield() o hasta que pasa a estado no ejecutable (parado). Un hilo hereda su prioridad del hilo que lo crea. La prioridad de un hilo se puede modificar con setPriority(int). Utilizando las constantes disponibles MIN_PRIORITY, NORM_PRIORITY, MAX_PRIORITY Si un hilo de alta prioridad se pone en ejecucin y no tiene nada de I/O puede acaparar todo el CPU (hilos egoistas, selfish threads) Es conveniente que el hilo ejecute yield() o sleep() a intervalos regulares para dar la oportunidad de que otros hilos puedan correr. yield() slo cede el CPU a hilos de igual prioridad. Sincronizacin de Hilos Es comn que varios procesos en ejecucin manipulen a un recurso compartido. Si los hilos que acceden recursos compartidos simplemente lo leen, entonces no hay necesidad de evitar que este sea utilizado por ms de un hilo a la vez. Sin embargo, cuando varios hilos comparten un objeto y este puede ser modificado por uno o ms de los hilos pueden ocurrir resultados indeterminados; en algunas ocasiones producir resultados correctos mientras que en otras los resultados sern incorrectos.
Para evitar esto, el recurso compartido se debe administrar de manera adecuada dndole a cada subproceso acceso exclusivo al cdigo que manipule al objeto compartido. Cuando un hilo se encuentre accediendo al recurso los dems hilos que tambin desean acceder el recurso se deben mantener en espera. Cuando el hilo que est accediendo al recurso compartido termina, a uno de los procesos que estn en espera se le permite continuar. A este proceso se le conoce
Qu pasa cuando diferentes hilos hacen acceso a recursos del programa, en este caso es necesario sincronizar este acceso para evitar inconsistencias en resultados, si dos hilos acceden al mismo recurso, el orden de ejecucin es impredecible, depende de la mquina virtual adems de otros factores como la carga del sistema en este momento y la ejecucin de otros hilos. Java implementa un mecanismo de monitores, este concepto es el que se ve en algn curso de sistemas operativos; si un objeto se le asocia la palabra reservada sincronize automticamente a este objeto se le asocia un candado o un lock.
Un monitor permite el acceso serializado y no simultneo a un objeto, es decir controla que solamente un hilo est accediendo a un determinado recurso al mismo tiempo para evitar caer en inconsistencias. En otras palabras: Es necesario sincronizar el acceso a recursos compartidos para evitar inconsistencias en los resultados. Si dos hilos acceden a un mismo recurso, el orden de ejecucin es impredecible, depende de la mquina virtual y otros factores del sistema (carga, ejecucin de otros hilos, etc.) Java implementa un mecanismo de monitores, si un objeto es identificado con synchronized se le asocia un candado (lock). Un monitor permite el acceso serializado y no simultneo a un objeto.
Usando Synchronized En el momento en que un objeto quiera acceder a otro objeto que est sincronizado se pone en una fila de espera hasta que se quite el candado, solamente un hilo puede estar dentro de un objeto sincronizado.
Cuando se utiliza la sincronizacin en objetos se puede hacer en una instruccin en un conjunto de instrucciones o en un mtodo, la recomendacin es que siempre se haga a nivel mtodo.
Algo muy importante es que un objeto tiene solamente un candado si es que en un objeto que se quiere sincronizar, se sincronizan dos o ms mtodos se puede tener esos mtodos sincronizados pero solamente existe un candado, qu quiere decir esto?, que si algn otro objeto, en este caso un hilo llama a un mtodo sincronizado automticamente se pone el candado al objeto, es decir otro hilo no puede acceder a ese objeto por la llamada a otro mtodo aunque sea diferente al que est sincronizado.
La manera de liberar el candado en un objeto es simplemente terminar la ejecucin del mtodo sincronizado, el hecho de que se utilice sincronizacin tiene un impacto importante en el rendimiento del programa, as es que la sincronizacin se debe mantener un mnimo y solamente utilizarla cuando se necesite. Por ejemplo, hay ciertas asignaciones de datos primitivos que no vale la pena sincronizar por que se que son atmicas, es decir se realizan en una sola instruccin del CPU.
Concluyendo:
Hilos que quieran acceder a un objeto sincronizado se ponen en fila de espera hasta que el objeto quede desocupado (unlock) Slo un hilo puede estar dentro de un objeto sincronizado. Se recomienda utilizar synchronized a nivel mtodo, pero puede sincronizar cualquier seccin de cdigo (seccin crtica). Cada objeto tiene un slo candado, si un hilo entra a un mtodo sincronizado, el objeto pone el candado a todos los mtodos sincronizados, es decir ningn otro hilo puede ejecutar ningn mtodo sincronizado del objeto, hasta que el hilo poseedor del candado lo libere. La manera de liberar el candado en un objeto es cuando se termina la ejecucin del un mtodo sincronizado. No vale la pena sincronizar asignaciones de algunos datos primitivos porque son atmicas. Excesiva sincronizacin tiene un impacto relevante en el rendimiento
Ejemplo Synchronized
Para ejemplificar lo anterior se har una aplicacin que cuenta con tres clases importantes.
Una clase que va a representar a un objeto contador. Este objeto tendr simplemente un valor el cual se podr desplegar con dos mtodos: sumaUno y restaUno. El mtodo sumaUno incrementa en 1 el valor del contador y lo despliega junto al el letrero suma; el mtodo restaUno lo decrementa en 1 el valor del contador y lo despliega junto al letrero resta.
Intencionalmente estn separadas las dos lneas de cdigo pero en ejecucin debe de desplegarse en una sola lnea el letrero suma o resta y el valor del contador, esto se hizo as para demostrar la sincronizacin de objetos.
Las otras dos clases importantes van a ser hilos, la clase Suma simplemente lo que har en su mtodo run, ser un ciclo for que se ejecutar 10 veces donde se va a llamar el mtodo sumaUno de un objeto contador, el cual se pasa como parmetro. La clase Resta tambin ser otro hilo donde se le restar 1 al contador y se desplegar su valor llamando al mtodo restaUno de un objeto de la clase Contador.
En el ejemplo el contador ser el mismo objeto, es decir se crearn dos objetos uno Suma y otro Resta pero accedern exactamente al mismo contador, es decir cuando se incremente o se decremente y se despliegue el valor de contador se est hablando exactamente del mismo contador.
Como se puede ver tanto la clase Suma como la clase Resta extienden a Thread y como se puede ver en el mtodo main de la clase principal, despus de crear ambos hilos, uno Suma y otro Resta con exactamente el mismo contador simplemente arrancamos su ejecucin llamando al mtodo start correspondiente que como se sabe llama al mtodo run de ese hilo.
A continuacin se muestra el ejemplo descrito anteriormente. Observe que en este caso no se han sincronizado los hilos. public class Nosincronizado{ public static void main(String args[]){ Contador c = new Contador(); Suma s = new Suma(c); Resta r = new Resta(c); s.start(); r.start(); } } class Contador{ public int contador = 0; public void sumaUno(){ contador++; System.out.print("Suma: "); try{ Thread.sleep(500); }catch(InterruptedException e){} System.out.println(contador); } public void restaUno(){ contador--; System.out.print("Resta: "); System.out.println(contador); } } class Suma extends Thread{ Contador c; public Suma(Contador c){ this.c = c; } public void run(){ for(int i=0;i<10;i++) c.sumaUno(); }
class Resta extends Thread{ Contador c; public Resta(Contador c){ this.c = c; } public void run(){ for(int i=0;i<10;i++) c.restaUno(); } }
Una posible ejecucin de este programa sera la siguiente: e:\Curso Java\Ejemplos>Java NoSincronizado Suma: Resta: 0 0 Suma: Resta: 0 0 Suma: Resta: 0 Suma: 1 Suma: 2 Suma: 3 Suma: 4 Suma: 5 Resta: 4 Resta: 3 3 Resta: Suma: 3 3 Resta: Suma: 3 3 Resta: 2 Resta: 1 Resta: 0 e:\Curso Java\Ejemplos> En la ejecucin de la aplicacin se puede ver que sta no despliega lo que se espera. Hay que recordar que el programa debera desplegar el letrero (suma o resta) en la misma lnea que el valor del contador (ejemplo: suma:1, resta:0 etc.). Como se puede ver en la ejecucin hay muchos lugares donde se despliega suma y resta en la misma lnea, lo que pasa aqu es ambas clases o ambos objetos de Suma y de Resta acceden al objeto contador simultneamente y de repente se ejecuta el system.out.print de uno de los objetos de los hilos y despus entra la ejecucin del otro hilo, ya que el anterior se fue a estado de detenido porque est en una operacin de entrada salida entonces no es lo que se quera ver que se despliegue, la razn es que se tiene un acceso simultneo de un mismo recurso por dos hilos. Ejemplo con sincronizacin El siguiente ejemplo soluciona el problema del anterior ya que aqu se sincronizan los hilos. public class sincronizado{ public static void main(String args[]){ Contador c = new Contador(); Suma s = new Suma(c); Resta r = new Resta(c); s.start(); r.start(); } } class Contador{ public int contador = 0; synchronized void sumaUno(){ contador++; System.out.print("Suma: "); System.out.println(contador);
} synchronized void restaUno(){ contador--; System.out.print("Resta: "); System.out.println(contador); } } class Suma extends Thread{ Contador c; public Suma(Contador c){ this.c = c; } public void run(){ for(int i=0;i<10;i++) c.sumaUno(); }
class Resta extends Thread{ Contador c; public Resta(Contador c){ this.c = c; } public void run(){ for(int i=0;i<10;i++) c.restaUno(); } } Este cdigo es exactamente igual al anterior, lnea por lnea, la nica diferencia es que los mtodos sumaUno y restaUno de la clase Contador, estn sincronizados.
Qu quiere decir esto? Que una vez que algn objeto que normalmente es un hilo acceda al mtodo sumaUno automticamente se pone un candado en el objeto contador y ningn otro hilo puede acceder al mtodo sumaUno o al mtodo restaUno hasta que el primer hilo termine la ejecucin de sumaUno y esto sucede tambin cuando se llama al mtodo restaUno.
Entonces, cuando un hilo entra en ejecucin ya sea en el mtodo sumaUno o restaUno, automticamente se pone un candado al objeto y ningn otro objeto puede acceder a ninguno de los mtodos sincronizados, una vez que termina la ejecucin se quita el candado y entra el siguiente hilo que est en la fila de espera.
A continuacin se muestran dos ejecuciones en la que se puede observar que el conteo es completamente aleatorio, uno de los hilos le suma 1 al contador, el otro de los hilos le resta 1, pero ahora si el desplegado es como fue diseada en un principio.
Primera Ejecucin: e:\Curso Java\Ejemplos>Java Sincronizado Resta: -1 Suma: 0 Resta: -1 Suma: 0 Resta: -1 Suma: 0 Suma: 1 Suma: 2 Resta: 1 Suma: 2 Resta: 1 Suma: 2 Resta: 1 Suma: 2 Resta: 1 Resta: 0 e:\Curso Java\Ejemplos> Segunda ejecucin:
e:\Curso Java\Ejemplos>Java Sincronizado Suma: 1 Suma: 2 Suma: 3 Suma: 4 Suma: 5 Resta: 4 Suma: 5 Resta: 4 Suma: 5 Resta: 4 Suma: 5 Resta: 4 Suma: 5 Resta: 4 Resta: 3 Resta: 2 Resta: 1 Resta: 0