Detener un Thread temporalmente: Runnable – Not Runnable

El sistema operativo se ocupa de asignar tiempos de CPU a los distintos threads que se estén ejecutando simultáneamente. Aun en el caso de disponer de un ordenador con más de un procesador (2 ó más CPUs), el número de threads simultáneos suele siempre superar el número de CPUs, por lo que se debe repartir el tiempo de forma que parezca que todos los procesos corren a la vez (quizás más lentamente), aun cuando sólo unos pocos pueden estar ejecutándose en un instante de tiempo.

Los tiempos de CPU que el sistema continuamente asigna a los distintos threads en estado runnable se utilizan en ejecutar el método run() de cada thread. Por diversos motivos, un thread puede en un determinado momento renunciar “voluntariamente” a su tiempo de CPU y otorgárselo al sistema para que se lo asigne a otro thread. Esta “renuncia” se realiza mediante el método yield(). Es importante que este método sea utilizado por las actividades que tienden a “monopolizar” la CPU. El método yield() viene a indicar que en ese momento no es muy importante para ese thread el ejecutarse continuamente y por lo tanto tener ocupada la CPU. En caso de que ningún thread esté requiriendo la CPU para una actividad muy intensiva, el sistema volverá casi de inmediato a asignar nuevo tiempo al thread que fue “generoso” con los demás. Por ejemplo, en un Pentium II 400 Mhz es posible llegar a más de medio millón de llamadas por segundo al método yield(), dentro del método run(), lo que significa que llamar al método yield() apenas detiene al thread, sino que sólo ofrece el control de la CPU para que el sistema decida si hay alguna otra tarea que tenga mayor prioridad.

  1. Ejecutando el método sleep() de la clase Thread. Esto detiene el thread un tiempo pre-establecido. De ordinario el método sleep() se llama desde el método run().
  2. Ejecutando el método wait() heredado de la clase Object, a la espera de que suceda algo que es necesario para poder continuar. El thread volverá nuevamente a la situación de runnable mediante los métodos notify() o notifyAll(), que se deberán ejecutar cuando cesa la condición que tiene detenido al thread.
  3. Cuando el thread está esperando para realizar operaciones de Entrada/Salida o Input/Output (E/S ó I/O).
  4. Cuando el thread está tratando de llamar a un método synchronized de un objeto, y dicho objeto está bloqueado por otro thread.

Un thread pasa automáticamente del estado Not Runnable a Runnable cuando cesa alguna de las condiciones anteriores o cuando se llama a notify() o notifyAll().

La clase Thread dispone también de un método stop(), pero no se debe utilizar ya que puede provocar bloqueos del programa (deadlock). Hay una última posibilidad para detener un thread, que consiste en ejecutar el método suspend(). El thread volverá a ser ejecutable de nuevo ejecutando el método resume(). Esta última forma también se desaconseja, por razones similares a la utilización del método stop().

El método sleep() de la clase Thread recibe como argumento el tiempo en milisegundos que ha de permanecer detenido. Adicionalmente, se puede incluir un número entero con un tiempo adicional en nanosegundos. Las declaraciones de estos métodos son las siguientes:

public static void sleep(long millis) throws InterruptedException
public static void sleep(long millis, int nanosecons) throws InterruptedException

Considérese el siguiente ejemplo:

System.out.println («Contador de segundos»); int count=0;
public void run () { try {
sleep(1000);
System.out.println(count++);
} catch (InterruptedException e){}
}

Se observa que el método sleep() puede lanzar una InterruptedException que ha de ser capturada. Así se ha hecho en este ejemplo, aunque luego no se gestiona esa excepción.

La forma preferible de detener temporalmente un thread es la utilización conjunta de los métodos wait() y notifyAll(). La principal ventaja del método wait() frente a los métodos anteriormente descritos es que libera el bloqueo del objeto. por lo que el resto de threads que se encuentran esperando para actuar sobre dicho objeto pueden llamar a sus métodos. Hay dos formas de llamar a wait():

1. Indicando el tiempo máximo que debe estar parado (en milisegundos y con la opción de indicar también nanosegundos), de forma análoga a sleep(). A diferencia del método sleep(), que simplemente detiene el thread el tiempo indicado, el método wait() establece el tiempo máximo que debe estar parado. Si en ese plazo se ejecutan los métodos notify() o notifyAll() que indican la liberación de los objetos bloqueados, el thread continuará sin esperar a concluir el tiempo indicado. Las dos declaraciones del método wait() son como siguen:

public final void wait(long timeout) throws InterruptedException
public final void wait(long timeout, int nanos) throws InterruptedException

2.   Sin argumentos, en cuyo caso el thread permanece parado hasta que sea reinicializado explícitamente mediante los métodos notify() o notifyAll().

 public final void wait() throws InterruptedException

Los métodos wait() y notify() han de estar incluidas en un método synchronized, ya que de otra forma se obtendrá una excepción del tipo IllegalMonitorStateException en tiempo de ejecución. El uso típico de wait() es el de esperar a que se cumpla alguna determinada condición, ajena al propio thread. Cuando ésta se cumpla, se utilizará el método notifyAll() para avisar a los distintos threads que pueden utilizar el objeto.

Fuente: Aprenda Java como si estuviera en primero de la Universidad de Navarra