Problema

Il problema del produttore-consumatore è caratterizzato da due task: il produttore e il consumatore, che comunicano attraverso un buffer, il quale ha una capacità limitata:

Gli aspetti da gestire in questa situazione sono:

Schema della soluzione

Quando il buffer è pieno, il produttore non ha spazio per inserire i dati, quindi deve sospendere la sua esecuzione. Successivamente, quando il consumatore preleva un elemento dal buffer pieno, liberando uno spazio, esso “sveglia” il produttore, che potrà allora ricominciare a depositare elementi nel buffer.

Analogamente, il consumatore si deve sospendere quando il buffer è vuoto, poichè non ci sono dati da prelevare, e viene poi risvegliato da un produttore, dopo che quest’ultimo ha depositato un elemento nel buffer vuoto.

Questa soluzione può non essere implementata tramite le primitive di comunicazione tra thread: synchronized, wait(), notify(), e notifyAll(). Bisogna però fare attenzione a non introdurre race condition, deadlock, o anche semplicemente errori di logica appliativa.

Esempio di implementazione errata

Esempio di implementazione corretta

Per ottenere un’implementazione corretta, si definisce la classe CellaCondivisa in modo che:

A scopo illustrativo, viene aggiunto anche un metodo che restituisce il numero di elementi attualmente nel buffer; essendo di sola lettura, questo non ha bisogno di essere synchronized.

public class CellaCondivisa {
	private static final int BUFFER_SIZE = 1;
	private int numItems = 0;
	private int value;

	public synchronized int getItem() throws InterruptedException {
		if (numItems == 0) {
			// Se non c'è niente da leggere,
			// chi cerca di farlo viene fermato.
			wait();
		}
		numItems--;
		// Siccome il buffer non è più pieno,
		// viene svegliato un eventuale produttore in attesa.
		notify();
		return value;
	}

	public synchronized void setItem(int v) throws InterruptedException {
		if (numItems == BUFFER_SIZE) {
			// Se il buffer è pieno,
			// chi cerca di scrivere va in attesa.
			wait();
		}
		value = v;
		numItems++;
		// Adesso il buffer non è più vuoto,
		// quindi si sveglia un eventuale consumatore in attesa.
		notify();
	}

	public int getCurrentSize() {
		return numItems;
	}
}