Esercizi thread interference

1) (1 punto) Quale metodo viene invocato su un oggetto di tipo thread per mandare un thread in esecuzione?

2) (1 punto) Qual'è il metodo dell'interfaccia Runnable che deve essere implementato per creare una classe di tipo Runnable?

3) (1 punto) Scrivete il codice di un thread che scrive 5 volte "CIAO", compresa la parte per lanciare il thread in esecuzione.

4) (3 punti) Il metodo interrupt() invocato su una variabile di tipo thread che cosa genera se il thread sta eseguendo la sleep()?

5) (3 punti) All'interno di un thread t1 viene fatto partire un thread t2.

Quale metodo va invocato su t2 per far si che t1 interrompa la sua esecuzione finché t2 non sia terminato?

6) (2 punti) L'accesso a una variabile condivisa da parte di due o più thread si chiama:

- deadlock

- sequenza critica

- InterruptedException

7) (1 punti) Quali sono le due tecniche di base utilizzate da Java per evitare che l'accesso a una variabile condivisa da parte di più thread dia risultati inconsistenti?

8) (2 punti) Abbiamo una variabile di tipo Contatore con un metodo per decrementare il valore, decrement() e un metodo che restituisce il valore getValue();

Qui sotto l'esempio di come si deve comportare un oggetto di tipo Contatore.

Contatore contatore = new Contatore(5);
contatore.decrement();
int valore = contatore.getValue(); // ritorna valore=4
contatore.decrement();
valore = contatore.getValue(); // ritorna valore=3Java

Implementare la classe Contatore scrivendo il codice, in modo che l'accesso da parte di più thread non crei una sequenza critica nel caso due thread eseguano contemporaneamente l'operazione di decrement().

RISPOSTA:

Una semplice classe Contatore che implementa i due metodi come esposti sopra potrebbe essere:

public class Contatore {
    
    private int value;
    
    public Contatore(int value) {
        this.value = value;
    }

    public void decrement() {
        value--;
    }

    public int getValue() {
        return value;
    }

}

Ora per rendere sicura la sezione critica del metodo decrement() o lo rendo synchronized o in modo equivalente creo un blocco synchronized sull'oggetto (con this). Sotto le due versioni equivalenti.

// versione metodi synchronized

public class Contatore {

    private int value;
    
    public Contatore(int value) {
        this.value = value;
    }

    public synchronized void decrement() {
        value--;
    }

    public synchronized int getValue() {
        return value;
    }

}
// versione blocchi synchronized

public class Contatore {

    private int value;
    
    public Contatore(int value) {
        this.value = value;
    }

    public void decrement() {
        synchronized(this) {
            value--;
        }
    }

    public int getValue() {
        synchronized(this) {
            return value;
        }
    }

}

Anche il metodo getValue() è stato sincronizzato per garantire la relazione di happends-before su variabili condivisa. Vedi: Memory Consistency Properties.

9) (2 punti) Implementare una classe Contatore come quella precedente ma che permetta a due thread t1 e t2 a uno di accedere al metodo increment() e all’altro a quello getValue() contemporaneamente, mentre non possono accedere contemporaneamente a increment() o a getValue().

RISPOSTA:

La soluzione è avere due oggetti distinti su cui ottenere il lock utilizzando i blocchi synchonized:


public class Contatore {
    
    private int value;
    
    private Object obj1 = new Object();
    private Object obj2 = new Object();
    
    public Contatore(int value) {
        this.value = value;
    }

    public void decrement() {
        synchronized(obj1) {
            value--;
        }
    }

    public int getValue() {
        synchronized(obj2) {
            return value;
        }
    }

}

In questo modo un thread può acquisire il lock su obj1 per eseguire il metodo decrement() e contemporaneamente un altro thread può acquisire il lock su obj2 per eseguire il metodo getValue().

Vedi:

10) (4 punti) E’ data la classe Decrementer di tipo Thread che agisce su una variabile di tipo Contatore e decrementa il contatore finché non arriva a 0 e quindi termina l'esecuzione del thread (codice qui sotto).

public class Decrementer extends Thread {

		private Contatore contatore;

		public Decrementer(Contatore contatore) {
			this.contatore = contatore;
		}

		public void run() {
				
				while(true) {
						
						boolean isZero = (0 == contatore.getValue());  // step 1
						if(isZero)  // CONDIZIONE D'USCITA step 2
							return;
						else
							contatore.decrement();  // step 3
				
				}  // fine while
			
		}  // fine run

}

Immaginate due thread t1 e t2 di tipo Decrementer vengono mandati in esecuzione contemporaneamente passandogli una stessa variabile di tipo Contatore.

Domanda 1: Con quale flusso di esecuzione potrebbe succedere che i due thread, t1 e t2, non terminino non arrivando mai alla condizione d'uscita?

Domanda 2: Come si potrebbe cambiare il codice per far si che invece i due thread terminino?

SOLUZIONE

Last updated