Programación multihilo

Hilos

¿Qué es un thread?

  • La programación multihilo permite la ocurrencia simultánea de varios flujos de control (aun cuando exista un sólo procesador).
  • Cada uno de ellos puede programarse independientemente.
  • Puede realizar una trabajo distinto, idéntico o complementario.
  • Un thread es un hilo de ejecución que
    • se ejecuta en paralelo e independiente de otro hilo
    • puede trabajar sobre datos propios o datos compartidos
    • puede en un momento dado pararse, reiniciarse, sincronizarse o esperar a otros hilos.

Ejemplo

Varios hilos con un sólo procesador

Hilos

Funcionamiento básico

  • Además de los hilos independientes, podemos crear otros hilos que se ejecutan en paralelo al código de los demás hilos.
  • Los hilos ejecutan el código que se encuentra dentro del método run(), bien sea dentro del thread, bien dentro de otro objeto.
    • En el propio objeto Thread, se debe de sobreescribir el método run()
    • En otros objetos, deben de implementar la interfaz Runnable.

Clase Thread

  • Los hilos en java se trabajan con la clase Thread
  • Algunos constructores
  • Thread(): Crea un hilo con el nombre «Thread-[+n]»
  • Thread(String name): Crea un hilo con el nombre que se le pasa por parámetro.
  • Thread(Runnable target): Crea un hilo asociado al objeto target
  • Combinaciones de ambos
  • «Pool» de hilos

Clase Thread

Algunos métodos interesantes

  • static Thread currenThread(): devuelve el hilo en curso
  • String getName(): devuelve nombre del hilo.
  • void setName(String name): otorga nombre al hilo.
  • int getPriority(): devuelve la prioridad del hilo.
  • void setPriority(int new Priority)asigna prioridades relativas.
  • ThreadGroup getThreadGroup(): devuelve el grupo de hilos al que pertenece el hilo.
  • void run(): método que contiene el código del hilo.

Clase Thread

Puesta en marcha del hilo

  • Los hilos se lanzan llamando al método start()
                    Thread miThread = new Thread();
                    MiThread.start();
                  
  • Cuando se lanza el hilo, se hace una llamada al método run() correspondiente.
  • De esta manera, tenemos un thread que no hace anda, ya que el método run() por defecto está vacío.

Clase Thread

Creación de hilos

  • Existen dos formas de trabajar con Threads
    • Heredando de la clase Thread.
    • Implementando la interfaz Runnable

Clase Thread

Heredando de la clase Thread

  • Simplemente, se hereda de clase Thread y se sobrescribe el método run().

Clase Thread

Implementando la interfaz Runnable

  • La interfaz Runnable sólo tiene un método.
                    public interface Runnable{
                      void run();
                    }
                
  • El procedimiento es el siguiente:
    • Primero, creamos una clase que implemente la interface Runnable.
    • Implementando la interfaz Runnable
    • Creamos un objeto de la clase.
    • Vincular el objeto a un thread

Clase Thread

Heredando de la clase Thread

  • Ejemplo
                    class MiRunnable implements Runnable {
                      void run() {
                        // haz algo
                      }
                      public static void main( String[] args ) {
                        Runnable objeto = new MiRunnable();
                        Thread hilo = new Thread( objeto );
                        hilo.start();
                      }
                  

Ciclo de vida de un thread

Ciclo de vida de un thread

Estados

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
    • El método sleep() permite dejar dormido (not runnable) al thread en curso durante un tiempo determinado
    • En este estado, el thread no consume recursos
    • La temporización no es exacta!!
  • TERMINATED

Interrupción de hilos

  • Programar con hilos también acarrea varios peligros:
    • ¿Si varios acceden al mismo dato?
    • ¿Si varios ejecutan el mismo método?
    • Hay varios ejemplos en EjemploProblemasSincronización.java

Interrupción de hilos

  • Visto desde otro punto de vista.

Sincronización de hilos

  • Se utiliza el modificador synchronized.
  • Bloquea a los que no pueden «entrar». Hay un ejemplo en EjemploProblemasSincronizacion2.java
  • Puede aplicarse sobre:
    • Un método de instancia. En cada momento sólo habrá un hilo ejecutando un método sincronizado sobre cada objeto.
    • Un método de clase. En cada momento sólo puede haber un thread ejecutando un método sincronizado sobre la clase o cualquier objeto de la clase.
    • Un objeto tal cual
                      synchronized(obj);
                    

Entonces, sincronizamos todos los métodos y listo, no?

Sincronización de hilos

  • Es menos eficiente.
  • Puede dar lugar a interbloqueos

¿Qué es un interbloqueo?

Sincronización de hilos

  • Estado WAITING:
    • Método objeto.wait().
    • Causa que el thread en curso se quede esperando, ligado a un objeto
    • Hasta que otro thread lo libere
    • Métodos para liberar
      • objeto.notify(). Libera uno de los que está esperando.
      • objeto.notifyAll(). Libera a todos los que estén esperando.
  • Puede dar lugar a interbloqueos

Resumiendo

Ciclo de vida de un thread

  • NEW tras la construcción
  • RUNNABLE tras start()
    • BLOCKED Esperando a entrar en un synchronized (hasta que otro salga)
    • WAITING wait (esperando a un notify()); join (esperando a que termine otro/s)
    • TIMED_WAITING sleep (esperando un tiempo, también wait o join con timeout)
  • TERMINATEDal acabar el run().

Resumiendo

SWING

  • No es «thread-safe». Por ello, lo seguro es que sólo él acceda a los elementos visuales (ventanas y componentes en ventanas)
  • Cuidado con las llamadas a SWING desde fuera de los eventos.
  • Hay tres tipos de threads para swing:
    • El thread inicial (el main)
    • El hilo de gestión de eventos (event dispatcher)
    • Hilos de trabajo (worker threads), si procede.

Resumiendo

Hilo inicial

  • Es el del programa (main). Incluso la creación de ventanas puede hacerse de un modo seguro:
    • SwingUtilities.invokeLater( Runnable )
  •             public static void main(String[] args) {
                  SwingUtilities.invokeLater( new Runnable() {
                    public void run() {
                      createAndShowGUI();
                    }
                  });
                }
                

Resumiendo

Hilo de gestión de eventos

  • Todos los componentes de Swing (excepto los explícitamente "thread-safe" que son unos pocos) deben ser llamados desde este hilo.
    • Deberían ser tareas pequeñas.
    • Deberían acabar rápido.
    • Si no es así, la gestión de eventos se congela y el IU no responde.
  • Para tareas largas hacer la gestión desde fuera del hilo
    • Cuando se necesite acceder a un componente Swing, invokeLater
    • Si no se supiera si estamos o no en ese hilo: javax.swing.SwingUtilities.isEventDispatchThread()

Resumiendo

Hilos trabajadores

  • Para integrarse con Swing se pueden usar threads normales que invoquen runnables de Swing cuando lo necesiten
  • Pero también pueden usarse worker threads
    • Es una plantilla predefinida de hilo que interactúa con Swing:
      • doInBackground()como un run() que devuelve result. Puede llamar a publish( valor )
      • process( cola )recoge la cola de publish (lo llama el hilo de gestión de eventos)
      • done(). Lo llama el hilo de gestión de eventos.

Resumiendo

Consejos Swing/hilos

    • Sólo utilizar componentes Swing desde el hilo de Swing salvo métodos thread-safe
      • En particular, los consultores son siempre más seguros que los modificadores
      • invokeLater( r ).
      • invokeAndWait( r )
    • Código de los gestores de eventos: breve
    • Crear otro hilo desde el gestor si hace falta un código largo (a "mano" o usando SwingWorker)

Referencias