Home
Home
Tutorials
   PHP
   Java
   Tutti...
Manuali
SmartImage
Marketing
Downloads
Contatti
Affiliati

  Da vedere
Hosting
Statistiche per siti
Corsi per webmaster
Hardware
Processori


  OnFocus
Modificatori di visibilitá

JSON e PHP: Un esempio concreto

Mambo: Unknown column 'c.access' in 'on clause'

  Siti Amici
Miki News
Giochi gratis
Areagratis
Baratto Online
AI Machines
Guide e Manuali Gratis
FreeOnLine
FindIT
InfAdu
SoftGame.it
Visure camerali
Posizionamento Google - Come essere ai primi posti su Google
Add to Technorati Favorites

Tutti i siti amici
Diventa Affiliato

 


Autore: Claudio Venturini
Categoria: java
Livello: normale Livello normale

Cenni di programmazione multi-threading - parte 3

Gestire i thread - Il modello produttore-consumatore

Nella seconda parte di questa serie di tutorial abbiamo visto come due o più thread in esecuzione parallela possono accedere in lettura e scrittura ad una variabile condivisa, in maniera atomica. In questa terza parte vedremo uno dei più classici scenari di uso della concorrenza, il modello produttore - consumatore.

Modifichiamo l'esempio del web server in questo modo. Supponiamo che esista un thread (o più di uno, ma non è il caso di un web server reale), che riceva le richieste dall'esterno. Per fare in modo che il nostro server sia subito pronto a ricevere altre richieste, decidiamo che questo thread non le gestisce direttamente: le deposita in un buffer. Dopodichè avremo altri thread, o anche uno solo, che si occuperanno di prelevare le richieste dal buffer e gestirle. In questo esempio il produttore è rappresentato dal thread che inserisce le nuove richieste nel buffer, mentre il consumatore è il thread che estrae le richieste dal buffer per gestirle. Ovviamente possono esistere più thread produttori e più thread consumatori.

Chi gestisce il buffer?

Poichè il buffer è unico, deve esistere un soggetto che lo gestisca, e controlli di non perdere richieste e di non dare la stessa richiesta a più thread consumatori. Per questo si introduce il concetto di monitor. Il monitor è un semplice oggetto che incapsula una qualche struttura dati che funge da buffer. In questo esempio useremo un Vector contenente oggetti String, le nostre richieste. I problemi che is pongono sono essenzialmente due. Se il buffer è vuoto, come informo i consumatori che non c'è nessuna richiesta da gestire? E viceversa, se il buffer non è vuoto, come informo i consumatori che esiste una richiesta da gestire? La risposta più semplice è: non li informo, gestisco la loro esecuzione. Più precisamente andrò ad "addormentare" i consumatori se il buffer è vuoto, mentre li risveglierò se ricevo una nuova richiesta.

I metodi wait() e notify()

Il monitor, poichè gestisce il buffer, fornirà dei metodi per il prelevamento di una richiesta dal buffer, e per l'inserimento di una richiesta nel buffer, ovvero prelevaRichiesta() e accodaRichiesta(). Poichè il buffer è una risorsa condivisa questi metodi dovranno essere synchronized.

Supponiamo che un cosumatore tenti di prelevare una richiesta. Il monitor controllerà se ci sono richieste nel buffer, e se non ci sono dovrà addormentare il consumatore. A questo scopo si usa il metodo wait(), che addormenta il thread che ha chiamato il metodo. In pratica si addormenta il thread in attesa che nel buffer arrivi qualcosa.
Supponiamo ora che il produttore tenti di accodare un richiesta. Il monitor inserirà la richiesta nel buffer e dovrà risvegliare eventuali thread dormienti in modo da gestire la richiesta. Esiste quindi il metodo notify(). Esso risveglia il primo thread che era stato addormentato. Usando invece notifyAll() si svegliano tutti i thread in attesa. In questo caso bisogna ricordarsi di riaddormentare tutti quelli per cui non si hanno richieste da gestire.

Il codice

Passiamo al codice. Il monitor sarà di questo tipo:

import java.util.Vector;

public class 
Monitor {
  
  
// Coda delle richieste
  private Vector<StringcodaRichieste = new Vector<String>();
  
  
// Preleva la prima richiesta in coda
  public synchronized String prelevaRichiesta(){
    while (codaRichieste.size() == 0){
      try {
        wait();
      }
      catch (InterruptedException e){
        e.printStackTrace();
      }
    }
    
    return codaRichieste
.remove(0);
  }

  // Accoda una nuova richiesta
  public synchronized void accodaRichiesta(String richiesta){
    codaRichieste.addElement(richiesta);
    notifyAll();
  }

Notare che nel metodo prelevaRichiesta() non si usa un if ma un while. Ciò permette di riaddormentare eventuali thread svegliati con il notifyAll() del metodo accodaRichiesta() per cui non ci sono richieste. Infatti questi thread verrebbero svegliati, il ciclo while riprenderebbe e ricontrollerebbe la dimensione del buffer. Se questo è in realtà ancora vuoto i thread verrebbero nuovamente addormentati.

E ora i due thread produttore e consumatore, molto simili anche se svolgono azioni nettamente differenti:

public class MyThreadProduttore extends Thread{
  
  private Monitor monitor
;
  private int id;

  public void run(){
    int richiesta 1;
    while (true){
      try {
        Thread.sleep(300);
      }
      catch (InterruptedException e){};

      // accodo una richiesta
      monitor.accodaRichiesta("Produttore " id ", richiesta " richiesta);
      richiesta++;
    }
  }

  public MyThreadProduttore(Monitor monitorint id){
    this.monitor monitor;
    this.id id;
  }
}

public class 
MyThreadConsumatore extends Thread {
  
  private Monitor monitor
;
  private int id;

  public void run(){
    while (true){
      // prelevo la prima richiesta in coda
      String richiesta monitor.prelevaRichiesta();
      try {
        Thread.sleep(300);
      }
      catch (InterruptedException e){};

      System.out.println(richiesta);
      // ... azioni di gestione della richiesta ...
    }
  }

  public MyThreadConsumatore(Monitor monitorint id){
    this.monitor monitor;
    this.id id;
  }

Il metodo main è simile a quello della lezione scorsa, eccetto che crea anche i thread produttori:

public class MyMultiThreadedProgram {
  
  public 
static void main(String args[]){
    // creo il monitor
    Monitor monitor = new Monitor();
    try {
      // creo due thread di creazione delle richieste
      MyThreadProduttore tp1 = new MyThreadProduttore(monitor1);
      MyThreadProduttore tp2 = new MyThreadProduttore(monitor2);
      
      
// Avvio i thread
      Thread.sleep(300);
      tp1.start();
      Thread.sleep(300);
      tp2.start();
      
      
// creo due thread di gestione delle richieste
      MyThreadConsumatore tc1 = new MyThreadConsumatore(monitor1);
      MyThreadConsumatore tc2 = new MyThreadConsumatore(monitor2);

      // Avvio i thread
      Thread.sleep(300);
      tc1.start();
      Thread.sleep(300);
      tc2.start();
    }
    catch (InterruptedException e){}
  }

E' stato assegnato ad ogni thread un id solamente per facilitare il riconoscimento al momento della stampa dei messaggi. Notare l'uso del metodo statico sleep() per auto-addormentare il thread in modo da poter vedere l'effetto reale a video, che altrimenti sarebbe troppo veloce.

Eseguendo il codice vedrete che le richieste arrivano in modo non sequenziale, in quanto i due produttori sono eseguiti in parallelo e quindi con tempi diversi. I consumatori tuttavia prelevano le richieste nell'ordine in cui sono state inserite nel buffer, e vengono addormentati e svegliati in base alla disponibilità di richieste.

Integrare il DBMS nell'applicazione Java con SQLite Precedente Indice Successivo Cenni di programmazione multi-threading - parte 4
Integrare il DBMS nell'applicazione Java con SQLite Cenni di programmazione multi-threading - parte 4