Hilos en Java(Threads) parte 4

Revisemos lo visto anteriormente...

- El programador de hilos decide qué hilo ejecutar o pausar en un momento dado.

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

- Existen algunos métodos de la clase java.lang.Thread que nos pueden ayudar a influenciar al programador de hilos a tomar una decisión, estos son: sleep(), join(), yield() y setPriority().

- Los estados en que un hilo puede estar son: nuevo(new), ejecución (runnable), ejecutándose(running), esperando/bloqueado/dormido (waiting/blocked/sleeping) y muerto (dead).

- Un hilo en estado muerto nunca podrá pasar nuevamente a estado a cualquiera de los otros estados.

Continuemos pues con los hilos en Java.

Más info tras el salto...



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;
}

public void retiroBancario(int retiro){
balance = balance - retiro;
}

}

public class PeligroCuenta implements Runnable{

private CuentaBanco cb = new CuentaBanco();

public void run(){
for(int x = 0; 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)
{
PeligroCuenta pl = new PeligroCuenta();
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.

Desastroso no?, mientras Luis estaba checando el estado de cuenta y vió que era posible el retiro, Manuel estaba retirando y viceversa, finalmente, Manuel verifcó que había 10 pesos en el saldo y decidió retirarlos, pero oh sorpresa! Luis los 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();
}

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 es hacerRetiro(), 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
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.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.

Un ejemplo de un bloque sincronizado puede ser así...

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");
}
}

Esto es todo lo que veremos con los hilos en Java en la parte 4. Si tienes alguna duda deja tu comentario.
Más sobre programación en Java aquí.

13 comentarios:

  1. Anónimo dijo...:

    Hola, escribo porque estoy estudiando en españa y necesitaba ejemplos y ayudas sobre hilos y tengo que decir que los tuyos son muchos mejores que los de los libros de clase y me ayudan mucho mas que mi profesor a entender todo esto.Gracias de verdad por compartir lo que sabes con todo el mundo.Y con esto no me refiero a este articulo solo sino a todos los demas también porque todos los que ví eran igualmente buenos.Un saludo

  1. Monillo007 dijo...:

    Qué tal!

    Agradezco tu comentario con respecto a los artículos que he escrito, me da gusto saber que el propósito de escribir lo más entendible que pueda lo estoy logrando.

    Seguiré compartiendo y escribiendo con respecto a programación y muchas otras cosas, recordemos que el conocimiento humano le pertenece al mundo.

  1. Anónimo dijo...:

    changos!
    cualquiera de los codigo no pudo estar mejor estructurados, sinceramente necesitaba saber como llevar a cabo una carrewra de hilos para mi materia de topicos selectos de programacion, y en este blog la encontre, es bueno que personas como usted no se reserven lo que saben, asi nosotrros tambien podemos saber,
    gracias.
    saludos, si tengo alguna duda se la harea llegar, espero me pueda ayudar, see you!

  1. Anónimo dijo...:

    Gracias por tan magnifica explicacion de seguro que apoyo lo que el primero comentario dice es mejor que los profesores, solo que tengo una duda ejemplo si tengo un puente y cuatro carros y cuatro botes y quiero que pasen 2 de cada uno al mismo tiempo entren y finalizen iguales luego otros dos.
    ej.

    Bote 1 Entra
    Bote 2 Entra
    Bote 1 Sale
    Bote 2 Sale
    Carro 1 Entra
    Carro 2 Entra
    Carro 1 Sale
    Carro 2 Sale
    Bote 3 Entra
    Bote 4 Entra
    Bote 3 Sale
    Bote 4 Sale

    Muchas gracias de antemano si me podrias ayudar a entender esto. Suerte

  1. Anónimo dijo...:

    no me quedo esta linea del codigo clara
    for(int x =0;x<>hacerRetiro(10));

  1. Anónimo dijo...:

    hola , esta muy interesante el ejemplo, pero si hayun problemita en el cogido

    for(int x =0;x<>
    hacerRetiro(10));

    hasta que valor final va ser la comparacion, no logro entender, si lo pongo como comentario la parte del for, solo hace una vez el retiro cada uno

    //for(int x =0;x<>

    bueno haber si lo completa esa parte, no se con q se va a comparar

  1. Buenos dias

    Bien, los ejemplos que se exponene son muy interesantes, pero me uno al comentario de Anonimo, en la parte del for:for(int x = 0; x <>
    hacerRetiro(10); el valor limite de x no esta definido o necesita una libreria para poderlo trabajar y en la clase local PeligroCuenta sale un error que no se como corregirlo. Muchas gracias!!

  1. Monillo007 dijo...:

    Qué tal a todos, tardé mucho en responder pero aquí está el bloque de código que se copió mal, básicamente es el método run() de la clase:

    public void run() {
    for (int x = 0; x <= 3; x++) {
    hacerRetiro(10);
    if (cb.getBalance() < 0) {
    System.out.println("La cuenta está sobregirada.");
    }
    }
    }

    Así que ahí tienen, listo para realizar las pruebas. Saludox.

  1. AndreeVela dijo...:

    Buenas noches, te felicito por la dedicación que pones en tus artículos, incluso en atender los comentarios. Soy estudiante de Ingeniería en software, me ha gustado mucho como estructuras el tema, ademas de que tus ejemplos son básicos (por lo tanto fáciles de entender). Espero continúes con esta labor que me parece tan valiosa.

  1. Anónimo dijo...:

    tu muy bien esta de 10 estos manuales.
    gracias

  1. Gracias por la información, tengo una duda respecto al post. ¿Cómo es que un objeto sólo puede tener un seguro? No entendí esa parte. Saludos!

  1. Pachac Kamayuq dijo...:

    Buenas, dos cosas:
    - En el bucle for del método run() está mal escrito el código.
    - Me ocurre que cuando ejecuto este programa ya modificado con el synchronized, en mi equipo siempre Luis realiza todas sus operaciones y después Manuel, con lo que nunca ocurre que Manuel pueda sacar dinero ya que Luis ha sacado antes todo el dinero.

  1. Pachac Kamayuq dijo...:

    Quería aclarar mi post anterior:
    -Ya vi que la errata estaba corregida en los comentarios.
    -Quería especificar que mi problema se basa en que Luis siempre realiza sus operaciones primero y cuando termina las realiza Manuel. Vamos que hasta que no acaba el primer hilo no se ejecuta el segundo, y esto es lo que me fastidia ya que nunca se me cruzan los hilos. Tengo entendido que esto depende de la máquina, ¿no?

Publicar un comentario

Este es un espacio abierto, puedes escribir lo que gustes respetando los siguientes puntos:

1.- Lo que escribas esté relacionado con el post, si gustas contactarme puedes hacerlo aqui.

2.- Todo es cuestionable, aunque ten en cuenta que existen formas de hacerlo, evita las agresiones y revisa tu lenguaje antes de publicar un comentario.

3.- Siempre hay tres verdades: tu verdad, mi verdad y la verdad, por lo que opiniones diferentes no necesariamente son equivocadas.

4.- Los comentarios son una forma de discusión abierta, por lo que al publicar uno, implícitamente entras a una discusión, con todo lo que esto representa.

5. Me reservo el derecho de eliminar comentarios que no respeten las condiciones mencionadas anteriormente.

Toma en cuenta que puedes utilizar emoticones en tu comentario, para ver una lista de los disponibles da clic en este enlace.

 
Monillo007 © 2010 | Designed by Trucks, Manual Bookmarking | Elegant Themes