Thread in Java

Il linguaggio Java implementa una forma semplificata dei Monitor. Come prima cosa vediamo come creare un thread in Java.

Creazione di Thread in Java

I thread in Java vengono creati estendendo la classe Thread e definendo il metodo run (il codice che il thread eseguirà). Per eseguire il nuovo thread è sufficiente invocare il metodo start.

Oppure, se c’è esigenza di estendere altre classi, i thread possono anche essere creati come oggetti della classe Thread, al cui costruttore viene passato un oggetto che implementa l’interfaccia Runnable, che richiede l’implementazione del metodo run.

Notare che in questo caso per stampare il nome del thread si deve usare il metodo currentThread() della classe Thread. Nella prima soluzione, invece, possiamo direttamente invocare getName() sull’oggetto corrente in quanto istanza della classe Thread.

Entrambi i programmi danno il seguente output:

Come per i thead POSIX, possiamo anche “addormentare” un thread Java, in questo caso il tempo è dato in millisecondi ed è necessario gestire l’eccezione InterruptedException sollevata da sleep nel caso il thread t venga interrotto ad esempio tramite t.interrupt().

Esercizio 1: creazione, interruzione e attesa di thread

Creare n thread, rallentarli tramite sleep(), provare ad interromperne l’attesa utilizzando t.interrupt() e attendere la terminazione con t.join() (analogamente a quanto si fa con i thread POSIX), dove t è l’oggetto thread. Aggiungere opportune print per osservare l’esecuzione.

(La soluzione è in basso, guardatela dopo aver provato da soli)

I Monitor di Java

In Java è implementata una forma semplificata dei monitor con le seguenti caratteristiche:

  • Ogni oggetto ha un mutex implicito utilizzato per garantire mutua esclusione sui metodi;
  • I metodi sono eseguiti in mutua esclusione solo se dichiarati synchronized;
  • Ogni oggetto ha un’unica condition implicita sulla quale si possono effettuare le operazioni standard wait(), notify(), notifyAll();
  • se il metodo è statico allora il mutex è a livello di classe invece che di oggetto;
  • è inoltre possibile sincronizzare parti di codice di un metodo non synchronized nel seguente modo:

    this indica a quale oggetto fare riferimento per ottenere il lock implicito. In questo caso è l’oggetto stesso ma è possibile indicare oggetti differenti e quindi sincronizzarsi utilizzando il mutex di tali oggetti.

NOTA: la sincronizzazione è “rientrante”: è possibile chiamare un metodo synchronized da un altro synchronized senza problemi (il mutex è già acquisito alla chiamata del primo metodo sincronizzato e viene mantenuto fino al ritorno del metodo o ad una eventuale terminazione prematura per via di un’eccezione non gestita)

Esercizio 2: sezione critica

Implementare due thread che lavorano su un contatore condiviso, osservare le usuali interferenze e rimediare ponendo il codice all’interno di metodi synchronized o all’interno del costrutto synchronized(this) { ... } . Suggerimento: mettere il contatore in una classe apposita (che fungerà da monitor) e implementare i metodi per l’incremento e la lettura/stampa del valore.

Esercizio 3: produttore e consumatore

Implementare il problema del produttore e consumatore in Java. Testarlo con tanti produttori e tanti consumatori.

Soluzioni

Ecco le soluzioni ai primi due esercizi.

Soluzione esercizio 1

che dà il seguente output:

Soluzione esercizio 2

Eseguirlo con o senza il `synchronized’ nel metodo incrementa per osservare le interferenze. Dimensionare MAX opportunamente.

2 thoughts on “Thread in Java”

  1. Salve professore, ho provato a svolgere l’esercizio 3, potrebbe essere una soluzione accettabile?c’è qualcosa che potrebbe non andare bene?
    Grazie, cordiali saluti.

    /*Classe che implementa il Monitor in cui è possibile riempire e svuotare un buffer da più thread in mutua esclusione*/
    public class Monitor {
        private int[] buffer;
        private int cont;                       /*Contatore che uso controllare il numero di elementi presenti nel buffer*/
        private int inserisci;                  /*Indice per l'inserimento di un nuovo elemento nel buffer*/
        private int preleva;                    /*Indice per prelevare un elemento dal buffer*/
        private final int DIM;
    
        public Monitor(int dimBuffer){
            DIM = dimBuffer;
            buffer = new int[dimBuffer];
            cont = 0;
            inserisci = 0;
            preleva = 0;
        }
    
        synchronized void riempi(int dato) throws InterruptedException{
            while(cont == DIM)                  /* Se il contatore è uguale a alla dimensione del buffer*/
                this.wait();                    /* allora significa che il buffer è pieno */
    
            buffer[inserisci] = dato;
            inserisci = (inserisci + 1) % DIM;
    
            cont++;
            this.notify();                      /* Sblocca il primo thread in attesa nella coda della condition*/
                                                /* e viene messo in attesa*/
        }
    
        synchronized int svuota() throws InterruptedException{
            while(cont == 0)                    /* Se il contatore è uguale a 0 allora significa che il buffer è vuoto*/
                this.wait();                    /* e non c'è nulla da svuotare*/
    
            int dato;
            dato = buffer[preleva];
            preleva = (preleva + 1) % DIM;
    
            cont--;
            this.notify();                      /* Sblocca il primo thread in attesa nella coda della condition*/
                                                /* e viene messo in attesa*/
            return dato;
        }
    }
    
    /*Classe che implementa il funzionamento dei thread produttori*/
    public class MyThreadProduttore extends Thread {
        Monitor pc;
    
        public MyThreadProduttore(Monitor pc){
            this.pc = pc;
        }
    
        public void run(){
            Random random = new Random();
            System.out.println("Sono il Produttore "+ getName());
            try {
                sleep(2000);
                for (int i = 0; i < 10; i++) {
                    int r = random.nextInt(100);
                    pc.riempi(r);/* Invoco il metodo riempi su pc */
                    System.out.println(getName() + " Ho prodotto " + r);
                }
            }catch (InterruptedException e){
                System.out.println(getName()+" Sono stato interrotto");
            }
        }
    }
    
    /* Classe che implementa il funzionamento dei thread consumatori*/
    public class MyThreadConsumatore extends Thread{
        Monitor pc;
    
        public MyThreadConsumatore(Monitor pc){
            this.pc = pc;
        }
    
        public void run(){
            System.out.println("Sono il Consumatore "+ getName());
            try {
                sleep(2000);
                for (int i = 0; i < 10; i++) {
                    int r = pc.svuota();
                    System.out.println(getName() + " Ho consumato " + r);
                }
            }catch (InterruptedException e){
                System.out.println(getName()+" Sono stato interrotto");
            }
        }
    }
    
    /*Classe del main*/
    public class Main {
        public static void main(String[] args) throws InterruptedException{
            final int N_CONSUMATORI = 10;
            final int N_PRODUTTORI = 10;
    
            MyThreadProduttore[] produttori = new MyThreadProduttore[N_PRODUTTORI];
            MyThreadConsumatore[] consumatori = new MyThreadConsumatore[N_CONSUMATORI;
    
            Monitor pc = new Monitor(100);
    
            /*Creazione dei thread produttori*/
            for(int i = 0; i < N_PRODUTTORI; i++){
                produttori[i] = new MyThreadProduttore(pc);
                produttori[i].start();
            }
    
            /*Creazione dei thread consumatori*/
            for(int i = 0; i < N_CONSUMATORI; i++){
                consumatori[i] = new MyThreadConsumatore(pc);
                consumatori[i].start();
            }
    
            /*Attendo i thread*/
            for(int i = 0; i < N_PRODUTTORI; i++){
                 produttori[i].join();
            }
    
            for(int i = 0; i < N_CONSUMATORI; i++){
                consumatori[i].join();
            }
        }
    }
    
  2. L’unico problema è che, a causa della assenza di conditions, hai produttori e consumatori nella stessa coda. Potrebbe succedere che la notify di un consumatore sblocchi un consumatore invece che un produttore, creando uno stallo (può succedere, ad esempio, se ci sono due consumatori in coda, arrivano due produttori e il primo entra notificando il primo consumatore. Quando il primo consumatore entra e fa la notify vorrebbe notificare un produttore ma il primo della coda è in realtà il secondo consumatore che si riblocca causando uno stallo). In questi casi usate sempre la notifyAll e lasciate che i thread si riblocchino usando la condizione del while.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.