Thread Java: impianto di produzione schede con seriale

Un esercizio completo e un po’ complesso. Vogliamo simulare un impianto di produzione di schede con tre macchinari. Ogni macchinario è identificato da un codice alfanumerico e produce, in contemporanea con gli altri macchinari, delle schede che sono idealmente rappresentate da un semplice numero seriale da apporvi, costruito dal codicemacchinario-seriale.

Il seriale è un terzo oggetto che deve essere costruito con un numero intero auto-incrementale ad ogni pezzo che viene realizzato dai macchinari. Il seriale, non è gestito direttamente dai macchinari, ma deve essere condiviso comunque tra tutti.

Ogni macchinario produce una scheda in un tempo casuale tra i 2 e 5 secondi, stacca e stampa un seriale con la modalità descritta. Ogni macchinario può produrre al massimo 100 schede al giorno, dopodiché arresta la sua esecuzione.

L’impianto conta 3 macchinari. Ogni giorno viene avviato l’impianto e i suoi macchinari con un tasto invio (prevedere un opportuno messaggio). Viene impostato un numero seriale da cui riprendere la stampa delle schede. Col tasto invio si può stoppare nuovamente la produzione in qualsiasi momento, lasciando che i macchinari finiscano la produzione della scheda che hanno eventualmente in corso.

Al termine della produzione giornaliera o dell’arresto preventivo, viene stampato il quantitativo complessivo di schede prodotte, comprese quelle concluse dopo l’arresto forzato.

La classe Seriale

In questo esercizio ci troviamo di fronte ad un buffer che deve essere condiviso tra tutti i thread in modo che ognuno possa attingere, aggiornare o visualizzare il dato condiviso ed aggiornato. Una pratica già vista ( a questo link, ad esempio ), è quella di dichiarare un attributo opportuno statico all’interno del codice del thread ma è una pratica scomoda e poco versatile se il buffer deve condividere più dati o una struttura dati complessa. Meglio in questi casi ricorrere ad una classe esterna che poi verrà composta con il macchinario in un attributo statico. La gestione del buffer, sarà comunque esterna, con codice più pulito. In questo caso l’esempio è banale, ma meglio abituarsi a questo approccio.

La classe è piuttosto banale. L’unica accortezza è quella di introdurre un elemento che ci consente di agire con e su gli attributi in modo concorrente, ovvero scrivere o leggere dal buffer dati aggiornati e coerenti tra tutti i thread che ci interagiscono. Ecco perché i metodi di scrittura, set e incrementa, così come lettura è bene abbiano la dicitura synchronized. Eccezione è fatta solo per il costruttore che verrà lanciato però solo una volta, prima di condividere ‘oggetto a tutti i thread come parametro.

package iisteramo.schedediretethread;

/**
 *
 * @author alfredo centinaro
 */
public class Seriale 
{
    int progressivo;

    public Seriale(int progressivo) 
    {
        this.progressivo = progressivo;
    }
    
    synchronized public int incrementa()
    {
       return this.progressivo++;
    }      

    synchronized public int getProgressivo() {
        return progressivo;
    }

    synchronized public void setProgressivo(int progressivo) {
        this.progressivo = progressivo;
    }
    
}

La classe Macchinario

E’ il vero cuore della nostro esercizio con i thread. L’idea alla base è semplice: un attributo per identificare il codice alfanumerico del macchinario, la quantità prodotta dal macchinario che si aggiorna ad ogni pezzo prodotto, un attributo booleano per vedere se la macchina sta producendo o per stopparla preventivamente, l’attributo di tipo Seriale statico (è importante che lo sia!) che sarà il buffer condiviso per attingere ad un seriale nuovo ad ogni scheda prodotta.

Il cuore del thread è il metodo produciScheda() che stacca un numero casuale per simulare l’attesa tra 2 e 5 secondi, preleva quindi un seriale nuovo per simulare la produzione e incrementa la quantità prodotta di una unità. Viene stampato il seriale macchina col seriale scheda. Questa operazione verrà fatta 100 volte all’interno del metodo run() o finché qualche agente esterno (l’operatore col tasto invio vedremo) non metta a falso l’attributo produci.

package iisteramo.schedediretethread;

import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author alfredo centinaro
 */
public class Macchinario extends Thread
{
    private String  idMacchina;
    private int     qtaProdotta; //max 100
    private boolean produci;
    
    public static Seriale serialeScheda;

    public Macchinario(String idMacchina, Seriale sc) 
    {
        this.idMacchina     = idMacchina;
        this.qtaProdotta    = 0;
        this.produci        = true;
        this.serialeScheda  = sc;
    }
    
    public boolean isProduci() {
        return produci;
    }

    public void setProduci(boolean produci) {
        this.produci = produci;
    }

    public String getIdMacchina() {
        return idMacchina;
    }

    public void setIdMacchina(String idMacchina) {
        this.idMacchina = idMacchina;
    }

    public int getQtaProdotta() {
        return qtaProdotta;
    }

    public void setQtaProdotta(int qtaProdotta) {
        this.qtaProdotta = qtaProdotta;
    }
   
    
    public void produciScheda()
    {
        //tempo variabile tra 2 e 5 secondi
        Random rand = new Random();
        int tempoProduzione = 1000 *(rand.nextInt(6)+2);
        try 
        {
            Thread.sleep(rand.nextInt(tempoProduzione));
        } 
        catch (InterruptedException ex) 
        {
            Logger.getLogger(Macchinario.class.getName()).log(Level.SEVERE, null, ex);
        }
        
        int serialeStampato= Macchinario.serialeScheda.incrementa();
        this.qtaProdotta++;
        
        System.out.println("Scheda: "+this.idMacchina + " - "+ serialeStampato);
    }        

    @Override
    public void run()
    {
        while (this.qtaProdotta < 100 && this.produci == true)
        {
            this.produciScheda();
        }    
    }
}

Classe Impianto

Forse questa è la meno intuitiva e anche non estremamente necessaria. L’esercizio base può infatti essere già ottimale facendo lanciare dal programma main/principale i thread dei macchinari. Aggiungiamo però una piccola chicca. Probabilmente è un po’ più avanzata del livello richiesto in un istituto tecnico, ma interessante per chi vuole approfondire l’argomento thread o lo sta studiando all’università.

La classe impianto, infatti, è un thread che gestisce i thread dei macchinari. Prende come parametro questi tre macchinari (ma potrebbero essere anche di più con un vettore), li avvia e li stoppa attendendo un input col tasto invio da tastiera. La classe, quindi, ha solo il metodo run con due cicli while. Uno attende e si stoppa dopo aver avviato i macchinari, ovvero fatto lo start. Un secondo ciclo while attende un nuovo input da tastiera, imposta quindi a falso l’attributo produci dei macchinari che, di fatto, li va a stoppare in sicurezza, quindi raccoglie col join la fine dei thread per stampare la quantità prodotta, sia che la produzione si sia arrestata naturalmente, sia che sia stata interrotta col tasto invio.

package iisteramo.schedediretethread;

import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author alfredo centinaro
 */
public class Impianto extends Thread
{
    Macchinario m1;
    Macchinario m2;
    Macchinario m3;

    public Impianto(Macchinario m1, Macchinario m2, Macchinario m3) {
        this.m1 = m1;
        this.m2 = m2;
        this.m3 = m3;
    }
    
    @Override
    public void run()
    {
        //avvio da tastiera
        while(true)
        {
            System.out.print("Premi invio per iniziare la produzione. Ripremilo per fermarla");
            try {
                char tastiera = (char) System.in.read();
            } catch (IOException ex) {
                Logger.getLogger(Impianto.class.getName()).log(Level.SEVERE, null, ex);
            }            
            m1.start();
            m2.start();
            m3.start();
            break;
        }    
        

        //interruzione da tastiera
        while(true)
        {
            try {
                char tastiera = (char) System.in.read();
            } catch (IOException ex) {
                Logger.getLogger(Impianto.class.getName()).log(Level.SEVERE, null, ex);
            }
            m1.setProduci(false);
            m2.setProduci(false);
            m3.setProduci(false);
            
            try 
            {
                m1.join();
                m2.join();
                m3.join();

            } catch (InterruptedException ex) 
            {
                Logger.getLogger(Schedediretethread.class.getName()).log(Level.SEVERE, null, ex);
            }
            
            this.interrupt();         
            break;
        }   
        
        //stampa qta prodotta sia che sia stoppato che conclusione normale
        int qta = m1.getQtaProdotta() + m2.getQtaProdotta() + m3.getQtaProdotta();
        System.out.println("\n Quantità prodotta: " + qta);         
    }
}

La classe main

Il lettore può già immagina: la classe main a questo punto serve o fa davvero poco rispetto ai nostri esercizi classici! Basta inizializzare Seriale, i tre Macchinari e il thread Impianto. L’unico thread da avviare è però quello dell’impianto, mentre i macchinari saranno avviati e gestiti dal thread impianto. Non occorrono altre particolari istruzioni per completare il nostro esercizio.

package iisteramo.schedediretethread;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author alfredo centinaro
 */
public class Main 
{

    public static void main(String[] args) 
    {
        //TO DO chiederlo da tastiera?
        Seriale s = new Seriale(100);
        
        Macchinario m1 = new Macchinario("M001",s);
        Macchinario m2 = new Macchinario("M002",s);
        Macchinario m3 = new Macchinario("M003",s);

        Impianto imp = new Impianto(m1,m2,m3);
        imp.start();

    }
}

Listati

Il codice completo su GitHub -> https://github.com/alfredocentinaro/esercizi-java/tree/main/thread-impianto

Eseguilo su Replit -> https://replit.com/@AlfredoCentina2/ThreadImpianto

Ultima modifica 20 Maggio 2022