• No se han encontrado resultados

Crear un hilo (Thread)

N/A
N/A
Protected

Academic year: 2018

Share "Crear un hilo (Thread)"

Copied!
23
0
0

Texto completo

(1)

Página 1 Para hablar de hilos en Java primeramente se necesita entender lo que es un hilo. Un hilo es un proceso que se está ejecutando en un momento determinado en nuestro sistema operativo, como cualquier otra tarea, esto se realiza directamente en el procesador. Existen los llamados 'demonios' que son los procesos que define el sistema en sí para poder funcionar y otros que llamaremos los hilos definidos por el usuario o por el programador, estos últimos son procesos a los que el programador define un comportamiento e inicia en un momento específico.

En Java, el proceso que siempre se ejecuta es el llamado main que es a partir del cual se inicia prácticamente todo el comportamiento de nuestra aplicación, y en ocasiones a la aplicación le basta con este solo proceso para funcionar de manera adecuada, sin embargo, existen algunas aplicaciones que requieren más de un proceso (o hilo) ejecutándose al mismo tiempo (multithreading), por ejemplo, se tiene una aplicación de una tienda departamental de la cual se actualizan los precios y mercancías varias veces al día a través de la red, se verifican los nuevos descuentos y demás pero que a su vez es la encargada de registrar las compras y todos movimientos que se realice con la mercancía dentro de la tienda, si se decide que dicha aplicación trabajará de la manera simple y con un solo proceso (o hilo), el trabajo de la actualización de precios y mercancías debe de finalizar antes de que alguien pueda hacer algún movimiento con un producto dentro de la tienda, o viceversa, ya que la aplicación no es capaz de mantener el proceso de actualización en segundo plano mientras se registra un movimiento. Si se toma este modelo mono-hilo el tiempo y dinero que se perderá dentro de la tienda será muchísimo mayor comparando con un modelo multi-hilo. En un modelo multi-hilo se pueden realizar todas las actualizaciones en segundo plano mientras se registra una o más ventas o movimientos, cada proceso independiente del otro viviendo o ejecutándose al mismo tiempo dentro de la misma aplicación.

(2)

Página 2 funcionamiento del procesador, basta con entender que en Java, 2 o más procesos pueden ejecutarse al mismo tiempo dentro de una misma aplicación y para ello son necesarios los Threads o hilos.

Ahora que ya entendemos lo que son los hilos pasaremos a una definición un poco más específica de Java. En Java un hilo o Thread puede ser 2 cosas:

+ Una instancia de la clase java.lang.Thread + Un proceso en ejecución

Una instancia de la clase java.lang.Thread, no es más que cualquier otro objeto, con variables y métodos predefinidos. Un proceso en ejecución es un proceso individual que realiza una tarea o trabajo, tiene su propia pila de información independiente a la de la aplicación principal.

Es necesario entender que el comportamiento de los hilos o threads varía de acuerdo a la máquina virtual, incluso el concepto más importante a entender con los hilos en Java es que...

"Cuando se trata de hilos, muy pocas cosas está garantizadas"

...por ello se debe ser cautelosos al momento de interpretar el comportamiento de un hilo. Pasemos al código.

Crear un hilo (Thread)

Un hilo o proceso en Java comienza con una instancia de la clase java.lang.Thread, si analizamos la estructura de dicha clase podremos encontrar bastantes métodos que nos ayudan a controlar el comportamiento de los hilos, desde crear un hilo, iniciarlo, pausar su ejecución, etc. Aquellos métodos que siempre tenemos que tener presentes con respecto a los hilos son:

(3)

Página 3 La acción sucede dentro del método run(), digamos que el código que se encuentra dentro de dicho método es el trabajo por hacer, por lo tanto, si queremos realizar diversas operaciones cada una simultánea pero de manera independiente, tendremos varias clases, cada una con su respectivo método run(). Dentro del método run() puede haber llamados a otros métodos como en cualquier otro método común, pero la pila de ejecución del nuevo proceso siempre comenzará a partir de la llamada al método run().

Definir un nuevo hilo

Para definir e instanciar un nuevo Thread (hilo, proceso) existen 2 formas:

+ Extendiendo (o heredando) a la clase java.lang.Thread + Implementando la interfaz Runnable

Normalmente en el trabajo diario es más recomendable el implementar la interfaz Runnable, en lugar de extender la clase java.lang.Thread debido a que una clase solamente puede heredar o extender otra sola clase pero puede implementar muchas interfaces. Si extendemos de la clase java.lang.Thread no podremos extender o heredar ninguna otra, pero si implementamos la interfaz Runnable, podremos heredar cualquier otra clase e implementar muchas otras interfaces sin perder el comportamiento de la nuestra.

En cualquiera de los 2 casos, ya sea heredando de java.lang.Thread o implementando Runnable, al definir e instanciar un nuevo hilo, necesitaremos de redefinir el método run(), veamos cómo hacerlo.

Extendiendo java.lang.Thread

(4)

Página 4 P. ej.

class MiHilo extends Thread{

public void run(){

System.out.println("Trabajo por hacer dentro de MiHilo");

} }

Nuevamente, esto no es recomendable ya que al heredar la clase Thread, no podrás heredar nada más.

Toma en cuenta que puedes sobrecargar el método run() sin ningún problema como se muestra a continuación...

class MiHilo extends Thread{ public void run(){

System.out.println("Trabajo por hacer dentro de MiHilo"); }

public void run(String s){

System.out.println("La cadena ingresada es " + s); }

}

(5)

Página 5 Implementando la interfaz Runnable::

1.- Implementa a la interfaz Runnable por medio de la palabra 'implements'. 2.- Redefine el método run().

P. ej.

class MiHilo implements Runnable{ public void run(){

System.out.println("Trabajo por hacer dentro de MiHilo"); }

}

Independientemente de cómo definas tu hilo (por medio de extends Thread o implements Runnable), el hilo tendrá el mismo comportamiento.

Instanciando un hilo o Thread

Debemos recordar que cada hilo de ejecución es una instancia de la clase Thread, independientemente de si tu método run() está dentro de una subclase de Thread o en una implementación de la interfaz Runnable, se necesita un objeto tipo Thread para realizar el trabajo.

Si has extendido la clase Thread, el instanciar el hilo es realmente simple:

MiHilo h = new MiHilo();

Si has implementado la interfaz Runnable, es un poquito más complicado, pero solo un poco:

MiHilo h = new MiHilo();

Thread t = new Thread(h); // Pasas tu implementación de Runnable al nuevo Thread

(6)

Página 6 public class PruebaHilos{

public static void main (String[] args) {

MiHilo h = new MiHilo(); Thread t1 = new Thread(h); Thread t2 = new Thread(h); Thread t3 = new Thread(h); }

}

El pasar un solo objeto tipo MiHilo a varios objetos tipo Thread significa que el trabajo que se encuentra dentro del método run() de MiHilo se realizará en

diversas ocasiones, o lo que es lo mismo, varios hilos realizarán el mismo trabajo.

Existen diversos tipos de constructores a partir de los cuales se puede crear un hilo, algunos de los más importantes son:

+ Thread()

+ Thread(objetoRunnable)

+ Thread(objetoRunnable, String nombre) + Thread(String nombre)

Una vez que creamos e instanciamos un objeto tipo Thread, se dice que el objeto está en un estado 'new' o 'nuevo', es decir, ya existe pero aún no ha empezado con su trabajo, no está 'vivo'. Una vez que mandamos llamar al método start(), el hilo es considerado vivo o alive y se le considera como un hilo o proceso muerto una vez que el método run() fue completado. Existe un método para saber si el hilo está vivo o en ejecución, el método isAlive().

Comenzando con el trabajo

(7)

Página 7 hilo.start();

Se debe de llamar al método start() a través de una instancia de la clase Thread. Una vez que llamamos al método start() sucede lo siguiente:

+ Un nuevo proceso de ejecución comienza (con su propia pila de información). + El hilo o proceso cambia de estado nuevo o new a estado de ejecución o runnable.

+ Cuando el hilo tenga su turno de ejecutarse, el método run() del objeto al que refiere se ejecuta.

P. ej.

public class Hilos {

public static void main(String[] args){ System.out.println("Dentro de main..."); HiloNuevo hn = new HiloNuevo(); Thread nuevoHilo = new Thread(hn); nuevoHilo.start();

} }

class HiloNuevo implements Runnable{

public HiloNuevo(){

System.out.println("Comenzando un HiloNuevo...");

}

public void run(){

System.out.println("Llamando al método run de HiloNuevo..."); for(int i=0;i<5;i++)

{

System.out.println(i); }

(8)

Página 8 }

}

Al ejecutar el código anterior nos da un resultado como el siguiente:

Dentro de main...

Comenzando un HiloNuevo...

Llamando al método run de HiloNuevo... 0

1 2 3 4

Terminando el trabajo...

Es importante tomar en cuenta que al llamar el método start(), se ejecuta el trabajo definido en el método run() en un nuevo e independiente proceso, de igual manera podemos llamar directamente al método run() y el mismo trabajo se ejecutará pero no comenzará un proceso independiente y no tendrá sentido que utilicemos los hilos.

Para saber qué hilo se encuentra en ejecución en un momento determinado, existe el método estático Thread.currentThread().getName() el cual te devuelve un valor de tipo cadena con el nombre del hilo en ejecución, si no has definido un nombre con el método setName(), de igual manera el proceso lo tendrá, algo muy parecido a Thread-0.

Ejecutando varios hilos al mismo tiempo

Vamos a analizar un ejemplo de 3 hilos realizando el mismo trabajo, comenzaremos con una clase que implementa la interfaz Runnable, la cual es instanciada y enviada a 3 diferentes hilos, posteriormente le damos un nombre a cada uno de ellos e iniciamos su trabajo con el método start().

public class Hilos {

(9)

Página 9 HiloNuevo hn = new HiloNuevo();

Thread uno = new Thread(hn); Thread dos = new Thread(hn); Thread tres = new Thread(hn); uno.setName("Luis");

dos.setName("Carlos"); tres.setName("María"); uno.start();

dos.start(); tres.start(); }

}

class HiloNuevo implements Runnable{ public void run(){

for(int i=0;i<3;i++) {

System.out.println("Comenzado por " + Thread.currentThread().getName() + ", i = "+i);

} } }

Una vez que ejecutamos lo anterior nos dará un resultado parecido a lo siguiente: Comenzado por Luis, i = 0

Comenzado por Luis, i = 1 Comenzado por Luis, i = 2 Comenzado por Carlos, i = 0 Comenzado por Carlos, i = 1 Comenzado por Carlos, i = 2 Comenzado por María, i = 0 Comenzado por María, i = 1 Comenzado por María, i = 2

...¿o no?, si lo volvemos a ejecutar (al menos en mi máquina) obtengo lo siguiente:

(10)

Página 10 Comenzado por Carlos, i = 0

Comenzado por Luis, i = 1 Comenzado por Carlos, i = 1 Comenzado por Luis, i = 2 Comenzado por María, i = 0 Comenzado por María, i = 1 Comenzado por María, i = 2 Comenzado por Carlos, i = 2

Vaya sorpresa, cada vez que se ejecuta obtengo un resultado diferente, por lo tanto podemos definir que el comportamiento de los hilos no es predecible, anteriormente he mencionado que"cuando se trata de hilos, muy pocas cosas está garantizadas", lo cual significa que los hilos no necesariamente se ejecutarán en el orden en que fueron iniciados o que una vez que el hilo se inició no parará hasta terminar su trabajo, lo cierto es que nada está garantizado más que lo siguiente:

"Cada hilo se iniciará y se ejecutará hasta que se complete"

No hay un orden ni nada que garantice el funcionamiento de varios hilos al mismo tiempo, sin embargo, cada hilo en particular trabaja con normalidad, si observamos solamente el comportamiento del hilo de nombre Carlos, vemos que i va de 0 a 2, al igual que Luis y María, pero cuando todos trabajan junto, nada asegura que se ejecutarán desde el inicio hasta el final sin interrupciones, así trabajan los hilos en Java.

Debes de saber de igual manera que una vez que se ha llamado al método start() de un hilo, no puedes volver a realizar otra llamada al mismo método, independientemente de si el hilo ha terminado de realizar lo que está definido dentro de su método run() o no, si vuelves a llamar al método start() en un hilo que ya lo llamó previamente, obtendrás una excepción java.lang.IllegalThreadStateException.

(11)

Página 11 El programador de hilos

En la máquina virtual de Java existe algo que llamamos el programador de hilos que es el encargado de decidir qué hilo es el que se va a ejecutar por el procesador en un momento determinado y cuándo es que debe de parar o pausar su ejecución. Asumiendo que se tiene un equipo de un solo procesador, solo un proceso puede estar corriendo en un momento dado, como lo hemos mencionado anteriormente, la idea de multi-proceso o multithreading es meramente aparente. Para que un hilo sea elegible para ser ejecutado, este debe de estar en estado en ejecución (runnable), si se encuentra en cualquier otro estado no podrá ser elegible por el programador de hilos. Es importante tomar en cuenta que...

"El orden en que un hilo en estado de ejecución es escogido por el programador de hilos no es garantizado".

...aunque no podemos controlar al programador de hilos, en algunas ocasiones podemos influenciarlo mandando a llamar algunos métodos contenidos en la clase java.lang.Thread.

Los métodos de la clase java.lang.Thread::

Algunos métodos de esta clase nos pueden ayudar a influenciar al programador de hilos a tomar una decisión sobre qué hilo ejecutar en qué momento. Los métodos son los siguientes:

+ public static void sleep(long milisegundos) throws InterruptedException + public static void yield()

+ public final void join() throws InterruptedException + public final void setPriority(int nuevaPrioridad)

Nota: el método sleep() y join() tienen versiones sobrecargadas que no hemos mencionado.

Algunos métodos de la clase java.lang.Object

(12)

Página 12 defecto. En este caso analizaremos algunos de ellos que nos son útiles en el manejo de hilos:

+ public final void wait() throws InterruptedException + public final void notify()

+ public final void notifyAll()

El método wait() define 3 versiones sobrecargadas, incluyendo la mencionada anteriormente.

Antes de mencionar el comportamiento de los métodos anteriormente mencionados, analicemos algunos estados más en los que un hilo se puede encontrar y sus respectivas transiciones.

Estados de los hilos y transiciones

Ya hemos visto 3 estados de los hilos

-

nuevo

, en

ejecución

y

muerto

- pero hay algunos otros bastante importantes. Analicemos cada uno de ellos:

- Nuevo (new): Este es el estado en que un hilo se encuentra después de que un objeto de la clase Thread ha sido instanciado pero antes de que el

método start() sea llamado.

- En ejecución (runnable): Este es el estado en que un hilo puede ser elegido para ser ejecutado por el programador de hilos pero aún no está corriendo en el procesador. Se obtiene este estado inmediatamente después de hacer la llamada al método start() de una instancia de la clase Thread.

- Ejecutándose (running): Este es el estado en el que el hilo está realizando lo que debe de hacer, es decir, está realizando el trabajo para el cual fue diseñado.

(13)

Página 13 - Muerto (dead): Un hilo está muerto cuando se han completado todos los procesos y operaciones contenidos en el método run(). Una vez que un hilo ha muerto NO puede volver nunca a estar vivo, recordemos que no es posible

llamar al método s

tart()

más de una vez para un solo hilo.

Prevenir la ejecución de un hilo

Hilo Durmiendo (sleeping):

El método sleep() es un método estático de la clase Thread. Generalmente lo usamos en el código para pausar un poco la ejecución de un proceso en particular forzándolo a dormir durante un tiempo determinado. Para forzar un hilo a dormir podemos usar un código parecido a lo siguiente:

try{

Thread.sleep(5*60*1000); //Duerme durante 5 minutos }catch(InterruptedException ex){}

Normalmente cuando llamamos al método sleep() encerramos el código en un try/catch debido a que dicho método arroja una excepción.

El hecho de que un hilo deje de dormir, no significa que volverá a estar ejecutándose al momento de despertar, el tiempo especificado dentro del método sleep() es el mínimo de tiempo que un hilo debe de dormir, aunque puede ser mayor. De igual manera se debe de tomar en cuenta que el método sleep() es estático, es decir, solo hay uno para toda la clase Thread, por lo tanto, un hilo no puede poner a dormir a otro, el método sleep() siempreSIEMPRE afecta al hilo que se encuentra ejecutando al momento de hacer la llamada.

Prioridades de hilo y el método yield()

(14)

Página 14 será desplazado al estado en ejecución para que un hilo de mayor prioridad pueda ejecutarse. De cualquier manera, nunca debes de basar el funcionamiento de tu aplicación basado en las prioridades de los hilos.

Para establecer la prioridad de un hilo debemos hacer algo parecido a lo siguiente:

HiloRunnable h = new HiloRunnable(); Thread t = new Thread(h);

t.setPriority(7); t.start();

Algunas máquinas virtuales no son capaces de reconocer 10 valores diferentes de prioridad, lo que causa un problema al momento de la ejecución, debido a ello, la clase Thread tiene 3 constantes (variables estáticas y finales) que definen un rango de prioridades de hilo:

+ Thread.MIN_PRIORITY (1) + Thread.NORM_PRIORITY (5) + Thread.MAX_PRIORITY (10)

:El método yield():

El método yield() tiene la función de hacer que un hilo que se está ejecutando de regreso al estado en ejecución(runnable) para permitir que otros hilos de la misma prioridad puedan ejecutarse. Sin embargo, el funcionamiento de este método (al igual que de los hilos en general) no está garantizado, puede que después de que se establezca un hilo por medio del método yield() a su estado en ejecución(runnable), éste vuelva a ser elegido para ejecutarse. El método yield() nunca causará que un hilo pase a estado de espera/bloqueado/dormido, simplemente pasa de ejecutándose(running) a en ejecución(runnable).

:El método join():

(15)

Página 15 espera de A. Esto significa que B nunca podrá ejecutarse si A no completa su proceso. En código se utiliza así:

Thread t = new Thread(); t.start();

t.join();

Existe una versión sobrecargada de join() que puede incluir el tiempo en milisegundos, por ejemplo si llamamos a t.join(5000), significa que el hilo que queremos ejecutar debe esperar a que el hilo que se está ejecutando en este momento termine, pero si tarda más de 5 segundos entonces debe dejar de esperar y entrar en estado de ejecución(runnable).

Ya que conocemos a fondo el funcionamiento de los hilos, analicemos un escenario interesante en una aplicación. Imaginemos que tenemos 2 hilos que están accediendo a la misma instancia de una clase, ejecutando el mismo método y accediendo incluso al mismo objeto y mismas variables, cada uno cambiando el estado primario de dicho objeto prácticamente de manera simultánea, lo mismo sucede con las variables. Si los datos que se están modificando indican al programa cómo funcionar (y normalmente así es, si no ¿para qué los queremos?) el pensar en esta situación originaría un resultado desastroso.

Imaginemos que 2 personas de una empresa , Luis y Manuel,van a realizar un retiro a una cuenta empresarial, lo primero que hace cada uno de ellos es checar el saldo, si el saldo es suficiente para el retiro, este se realiza con éxito, de lo contrario el sistema indica al usuario que no hay dinero suficiente. Ahora pensemos que las 2 personas lo van a realizar prácticamente al mismo tiempo. En código sería lo siguiente:

class CuentaBanco { private int balance = 50;

public int getBalance(){ return balance;

}

(16)

Página 16 }

}

public class PeligroCuenta implements Runnable{

private CuentaBanco cb = new CuentaBanco();

public void run(){

for(int x = 0; x <=10;x++)hacerRetiro(10); if(cb.getBalance()<0)

System.out.println("La cuenta está sobregirada."); }

private void hacerRetiro(int cantidad){ if(cb.getBalance()>=cantidad)

{

System.out.println(Thread.currentThread().getName()+" va a hacer un retiro."); try {

Thread.sleep(1000);

} catch (InterruptedException ex) { ex.printStackTrace();

}

cb.retiroBancario(cantidad);

System.out.println(Thread.currentThread().getName() + " realizó el retiro con éxito.");

}else{

System.out.println("No ha suficiente dinero en la cuenta para realizar el retiro Sr." + Thread.currentThread().getName());

System.out.println("su saldo actual es de "+cb.getBalance()); try {

Thread.sleep(1000);

} catch (InterruptedException ex) { ex.printStackTrace();

} } }

public static void main (String[] args) {

(17)

Página 17 Thread uno = new Thread(pl);

Thread dos = new Thread(pl); uno.setName("Luis");

dos.setName("Manuel");

uno.start(); dos.start();

} }

Si realizamos lo anterior, nos aparece un resultado parecido a lo siguiente:

Luis va a hacer un retiro. Manuel va a hacer un retiro. Manuel realizó el retiro con éxito. Luis realizó el retiro con éxito. Luis va a hacer un retiro. Manuel va a hacer un retiro. Manuel realizó el retiro con éxito. Manuel va a hacer un retiro. Luis realizó el retiro con éxito. Luis va a hacer un retiro. Luis realizó el retiro con éxito.

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de -10

Manuel realizó el retiro con éxito.

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de -10

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de -10

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de -10

La cuenta está sobregirada. La cuenta está sobregirada.

(18)

Página 18 acababa de retirar, sin embargo el retiro de Manuel también se completó dejando la cuenta sobregirada.

A dicho escenario se le llama "condición de carrera", cuando 2 o más procesos pueden acceder a las mismas variables y objetos al mismo tiempo y los datos pueden corromperse si un proceso "corre" lo suficientemente rápido como para vencer al otro.

¿Qué es lo que se hace en estas situaciones?. La respuesta es muy sencilla, lo único que tenemos que hacer es checar el estado de cuenta y realizar el retiro en un solo proceso, sin separarlos. No se puede garantizar que durante esta operación "atómica", o dicho de otra manera, inseparable, el hilo que se está ejecutando permanecerá en dicho estado hasta que se complete, lo que sí podemos garantizar es que ningún otro hilo accese a los mismos datos hasta que el primero termine de realizar la operación.

Entonces ¿qué hacemos para proteger los datos?. Dos cosas:

- Marcar las variables como privadas.

- Sincronizar el código que modifica las variables.

Para marcar las variables como privadas utilizamos los identificadores de control de acceso, en este caso la palabra private. Para sincronizar el código utilizamos la palabra synchronized. Dicho con código...

private synchronized void hacerRetiro(int cantidad){ if(cb.getBalance()>=cantidad)

{

System.out.println(Thread.currentThread().getName()+" va a hacer un retiro."); try {

Thread.sleep(1000);

} catch (InterruptedException ex) { ex.printStackTrace();

(19)

Página 19 cb.retiroBancario(cantidad);

System.out.println(Thread.currentThread().getName() + " realizó el retiro con éxito.");

}else{

System.out.println("No ha suficiente dinero en la cuenta para realizar el retiro Sr." + Thread.currentThread().getName());

System.out.println("su saldo actual es de "+cb.getBalance()); try {

Thread.sleep(1000);

} catch (InterruptedException ex) { ex.printStackTrace();

} } }

Retomemos lo anterior. El método que realiza las operaciones con las variables eshacerRetiro(), por lo tanto, es el método que necesitamos que sea privado y sincronizado. Todo lo demás permanece igual. Si lo ejecutamos, obtenemos algo parecido a lo siguiente:

Luis va a hacer un retiro. Luis realizó el retiro con éxito. Luis va a hacer un retiro. Luis realizó el retiro con éxito. Luis va a hacer un retiro. Luis realizó el retiro con éxito. Luis va a hacer un retiro. Luis realizó el retiro con éxito. Manuel va a hacer un retiro. Manuel realizó el retiro con éxito.

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0

(20)

Página 20 su saldo actual es de 0

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Luis su saldo actual es de 0

No ha suficiente dinero en la cuenta para realizar el retiro Sr.Manuel su saldo actual es de 0

La cuenta no se sobregiró y seguramente nunca lo hará. Verifíca que ambas personas siempre checaron el saldo y realizaron el retiro antes de que la otra pudiera checar el saldo.

Sincronización y seguro::

¿Cómo es que funciona la sincronización?. Con un seguro. Cada objeto en Java posee un seguro que previene su acceso, dicho seguro se activa únicamente cuando el objeto se encuentra dentro de un método sincronizado. Debido a que solo existe un seguro por objeto, una vez que un hilo ha adquirido dicho seguro, ningún otro hilo podrá utilizar el objeto hasta que su seguro sea liberado.

Entendamos entonces que un seguro está libre siempre y cuando ningún hilo haya ingresado a un método sincronizado de dicho objeto.

Algunos puntos clave con la sincronización y el seguro de los objetos son:

+ Solo métodos (o bloques) pueden ser sincronizados, nunca una variable o clase. + Cada objeto tiene solamente un seguro.

+ No todos los métodos de una clase deben ser sincronizados, una misma clase puede tener métodos sincronizados y no sincronizados.

+ Si una clase tiene ambos tipos de métodos, múltiples hilos pueden acceder a sus métodos no sincronizados, el único código protegido es aquel dentro de un método sincronizado.

+ Si un hilo pasa a estado dormido(sleep) no libera el o los seguros que pudiera llegar a tener, los mantiene hasta que se completa.

+ Se puede sincronizar un bloque de código en lugar de un método.

(21)

Página 21 public class Ejemplo{

public void hacerAlgo(){

System.out.println("No sincronizado");

syncronized(this){

System.out.println("Sincronizado"); }

}

}

Nota: cuando sincronizas un bloque de código debes de especificar el objeto del cual quieres obtener su seguro, si deseas actuar sobre la misma instancia sobre la que se está trabajando se utiliza la palabra this. P. ej.:

public syncronized void hacerAlgo(){

System.out.println("Sincronizado"); }

es equivalente a:

public void hacerAlgo(){

syncronized(this){

System.out.println("Sincronizado"); }

}

Lo último que necesitamos saber con los hilos es el cómo interactúan entre ellos, cómo es que se comunican cuando tienen o liberan el seguro de un objeto en particular. La clase Object cuenta con 3 métodos que ayudan a los hilos a comunicarse entre ellos, los métodos son:wait(), notify() y notifyAll(). Algo importante a tomar en cuenta con los hilos es:

(22)

Página 22 De la misma manera en que todo objeto posee un seguro, cada objeto puede tener una lista de hilos que están esperando una notificación del objeto. Un hilo se posiciona en esta lista de espera por medio de una llamada al método wait() del objeto al que está esperando. Desde ese momento, el hilo no ejecuta ninguna instrucción hasta que el método notify() es llamado por el objeto. Si existen varios hilos en la cola de espera del objeto, solo uno podrá ser escogido (sin un orden garantizado) por el programador de hilos para acceder a dicho objeto y obtener su seguro. Un ejemplo en código sería el siguiente:

public class ThreadA {

public ThreadA() { }

public static void main(String[] args) {

ThreadB b = new ThreadB(); b.start();

synchronized(b){ try {

System.out.println("Esperando a que B se complete..."); b.wait();

} catch (InterruptedException ex) { }

System.out.println("Total:" +b.total); }

} }

class ThreadB extends Thread{ long total;

public void run(){ synchronized(this) {

for(long i=0; i<1000000000;i++) total+=i;

(23)

Página 23 }

} }

Si lo ejecutamos obtenemos algo así...

Esperando a que B se complete... Total:499999999500000000

En caso de que omitiéramos la llamada a wait(), el resultado sería el siguiente:

Total:0

Un punto a tomar en cuenta es que para poder llamar al método wait(), se tuvo que sincronizar el bloque de código y obtener el seguro de b, de lo contrario se obtiene una excepción de tipo java.lang.IllegalMonitorStateException.

Como se ha mencionado anteriormente, un solo objeto puede tener una lista de espera de varios hilos, en dichos casos es recomendable utilizar notifyAll() en vez de notify(), ya quenotifyAll() notificará que está a punto de liberar el seguro del objeto a todos los hilos de la lista de espera.

Referencias

Documento similar

El presente trabajo de suficiencia profesional el cual lleva por título “Desarrollo de la conciencia fonológica para mejorar los procesos de lectura y escritura en

Es el proceso mediante el cual se realiza una elección entre diferentes alternativas o formas para resolver diferentes problemas a los que nos enfrentamos en la vida, estas se

&#34;No porque las dos, que vinieron de Valencia, no merecieran ese favor, pues eran entrambas de tan grande espíritu […] La razón porque no vió Coronas para ellas, sería

(1886-1887) encajarían bien en una antología de textos históricos. Sólo que para él la literatura es la que debe influir en la historia y no a la inversa, pues la verdad litera- ria

entorno algoritmo.

Habiendo organizado un movimiento revolucionario en Valencia a principios de 1929 y persistido en las reuniones conspirativo-constitucionalistas desde entonces —cierto que a aquellas

En este sentido, puede defenderse que, si la Administración está habilitada normativamente para actuar en una determinada materia mediante actuaciones formales, ejerciendo

En la parte central de la línea, entre los planes de gobierno o dirección política, en el extremo izquierdo, y los planes reguladores del uso del suelo (urbanísticos y