Salve a tutti,

Ho da poco iniziato ad avvicinarmi ai Thread utilizzando Java ed ho provato a fare un primo esercizio come esempio per capire come avviene la sincronizzazione.

L'esercizio prevede che si aggiunga ad una coda una sequenza di azioni ed esse vengono eseguite nell'ordine in cui sono state passate al metodo add (FIFO), una dopo l'altra, automaticamente.
Tuttavia mi si presenta un problema che non riesco a comprendere o cmq avrò fatto qualche errore percui posto di seguito il codice sperando che qualcuno mi possa aiutare

Il main con le chiamate:

codice:
public static void main(String[] args) {
		
		QueueOfTasks q = new QueueOfTasks();
		
		Runnable r1 = new Runnable() {
			public void run() {
				try { Thread.sleep(2000); }
				catch (InterruptedException e) { return; }
				System.out.println("Io adoro i thread!");
			}
		};
		
		Runnable r2 = new Runnable() {
			public void run() {
				System.out.println("Io odio i thread!");
			}
		};
		
		q.add(r1);
		q.add(r1);
		q.add(r2);
		System.out.println("fatto.");

	}
E la classe responsabile dell'esecuzione.

codice:
public class QueueOfTasks extends Thread {

	private LinkedList <Runnable> elenco = new LinkedList<Runnable>();	
	
	// costruttore
	public QueueOfTasks() {	
		new Thread(this);
	}
	
	public void add(Runnable r) {
		
		elenco.addLast(r);
		run();
		elenco.notifyAll();
	}
	
	public void run() {
			
		while(true) {
			synchronized(elenco) {
				if (elenco.isEmpty()) {
					try {
						elenco.wait();
					} catch (InterruptedException e) { return; }					
				} 	
				Runnable r = elenco.removeFirst();
				r.run();
				break;
			}						
		}	
	}

}

il problema è la seguente eccezione: java.lang.IllegalMonitorStateException.
Ho provato diversi metodi ma non riesco comunque a sincronizzare gli accessi in modo da ottenere il giusto output.
L'output corretto che dovrebbe uscire fuori è il seguente:

fatto.
Io adoro i thread! (dopo 2 secondi)
Io adoro i thread! (dopo 2 secondi)
Io odio i thread!

Chiedo venia per essermi eccessivamente dilungato