Ir al contenido principal

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í.

Comentarios

  1. 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

    ResponderBorrar
  2. 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.

    ResponderBorrar
  3. 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!

    ResponderBorrar
  4. 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

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

    ResponderBorrar
  6. 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

    ResponderBorrar
  7. 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!!

    ResponderBorrar
  8. 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.

    ResponderBorrar
  9. 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.

    ResponderBorrar
  10. tu muy bien esta de 10 estos manuales.
    gracias

    ResponderBorrar
  11. 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!

    ResponderBorrar
  12. 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.

    ResponderBorrar
  13. 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?

    ResponderBorrar

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.

Entradas más populares de este blog

3 sencillos pasos para tramitar la ayuda por desempleo de la AFORE

¿Tienes dinero ahorrado en tu AFORE y en este momento no estás trabajando o estás trabajando pero no tienes seguro social? Si respondiste sí a la pregunta anterior entonces hay una buena noticia para ti: puedes retirar una parte del dinero que tienes ahorrado. Y lo mejor, es muy sencillo. En este artículo te diré paso a paso qué hacer para obtener ese dinero, sin tecnicismos legales ni nada por el estilo, simple y sencillamente lo que necesitas saber. Paso Número 1: Obtener los últimos 2 estados de cuenta de tu Afore Esto en la mayoría de los casos es sumamente sencillo ya que dichos estados de cuenta llegan directamente al domicilio del ahorrador, si este es tu caso puedes saltar lo restante en este punto y continuar con el paso número 2, en caso contrario sigue leyendo… Si no tienes tus estados de cuenta debes ponerte en contacto con la empresa encargada de administrar tu ahorro para que te los proporcione, normalmente te van a pedir una identificación oficial, comprobante de

Conectar una base de datos en MySQL con NetBeans

NetBeans es una plataforma para el desarrollo de aplicaciones de escritorio usando Java y a un Entorno integrado de desarrollo (IDE) desarrollado usando la Plataforma NetBeans. Si eres un programador y desarrollas tus aplicaciones en Java seguramente necesitarás conectar una base de datos tipo MySQL a este entorno algún día, bueno, si lo necesitas ahora, he aquí una explicación paso a paso de cómo conectar ambas herramientas.

4 extraordinarias aplicaciones espía para Android

Le andas haciendo al James Bond y necesitas grabar video, voz o tomar fotos desde tu dispositivo Android sin ser notado? Aquí 4 excelentes aplicaciones que facilitarán tus hazañas de 007 en menos de lo que canta un gallo.

c606 c6nf5g4r6 e3 tec3ad6 [Como configurar el teclado]

¿Problemas al escribir con el teclado? ¿Tratas de escribir la letra ‘o’ y sale el número ‘6’ o algo por el estilo? La solución puede ser más simple de lo que parece. La solución a tu problema tras el salto...

Conexión, consulta y ejecución de sentencias en MySQL con Java

Anteriormente escribí este mismo artículo pero para bases de datos en Oracle . En este artículo prácticamente me copio y pego para explicar paso a paso cómo realizar la conexión a MySQL en Java, así como la forma de realizar consultas a los registros existentes y ejecutar sentencias de inserción, borrado y actualización de datos. Al final del artículo tendremos una clase que encapsulará todos los procedimientos necesarios para trabajar con la base de datos.