public interface Runnable { void run(); }Per attivare un thread si chiama il metodo start sul thread che a sua volta chiama il metodo run sul suo oggetto. Esempio:
class Count implements Runnable { String name; int min, max, incr; Count (String name, int min, int max, int incr) { this.name=name; this.min=min; this.max=max; this.incr=incr; } public void run() { for (int i=min; i < max; i+=incr) { System.out.println(name+" "+i); } } } class Test { public static void main(String[] args) { Thread th1 = new Thread(new Count("even",2,100,2)); Thread th2 = new Thread(new Count("odd",1,100,2)); th1.start(); th2.start(); } }Eseguendo Test, il programma crea due thread th1, th2, li attiva e termina. Ma poiché ci sono due thread attivi, l'esecuzione prosegue e finirà quando tutti i thread saranno finiti. I thread th1 e th2 sono eseguiti in parallelo. Questo vuole dire che sappiamo che th1 produrrà
even 2 even 4 ....e che th2 produrrà
odd 1 odd 3 ....ma non sappiamo in quale ordine ciò accadrà:
even 2 even 4 .... even 98 odd 1 ode 3 ...o
even 2 odd1 even 4 odd 3 ...o
odd 1 odd 3 even 2 even 4 odd 5 ...I thread infatti implementano Runnable. L'implementazione di default del metodo start è dunque quella di chiamare il metodo run sul suo oggetto. Si può ridefinire il run, cosí si può evitare di creare un oggetto. Un'alternativa all'esempio precedente è:
class Count extends Thread { String name; int min, max, incr; Count (String name, int min, int max, int incr) { this.name=name; this.min=min; this.max=max; this.incr=incr; } public void run() { for (int i=min; i < max; i+=incr) { System.out.println(name+" "+i); } } } class Test { public static void main(String[] args) { Thread th1 = new Count("even",2,100,2); Thread th2 = new Count("odd",1,100,2); th1.start(); th2.start(); } }
Se un thread è in stato d'attesa, lo si può svegliare chiamando il metodo interrupt. Tale metodo genera un'eccezione InterruptedException. Per questo è necessario ogni volta che si fa uno sleep prevedere l'eccezione InterruptedException.
Usando uno sleep, l'esempio precedente diventa:
class Count implements Runnable { ... public void run() { for (int i=min; i < max; i+=incr) { System.out.println(name+" "+i); try { Thread.currentThread().sleep(10); } catch (InterruptedException e) {} } } }Tale modifica rallenta l'esecuzione del programma Test, ma può anche modificare la combinazione dei due thread.
Un'alternativa equivalente a Thread.currentThread().sleep è usare il metodo statico Thread.sleep.
Si può aspettare fino alla fine di un thread utilizzando il metodo join. Questo metodo aspetta la fine del thread per proseguire. Per esempio se modifichiamo il nostro esempio:
class Test { public static void main(String[] args) { Thread th1 = new Thread(new Count("even",2,100,2)); Thread th2 = new Thread(new Count("odd",1,100,2)); th1.start(); th1.join(); th2.start(); } }Adesso siamo sicuri che i numeri pari sono stampati prima dei dispari.
class Speaker { synchronized void say(String s) { ... }La sincronizzazione è fatta al livello dell'oggetto. Dunque se un oggetto ha più di un metodo con synchronized, siamo sicuri che in ogni istante viene eseguito al massimo un metodo synchronized di questo oggetto:
class Point { int x, y; synchronized void set(int x, int y) { this.x = x; this.y = y; } synchronized void print() { System.out.println("("+x+","+y+")"); } }L'implementazione si fa con un lock sull'oggetto. In Java si può usare direttamente questo lock usando il commando:
synchronized (<EXPRESSION >) {
...
}
dove l'espressione si valuta in un oggetto. Dunque
synchronized void method() { .. }è equivalente a
void method() { synchronized (this) { ... } }
Per esempio, definiamo un produttore che genera messaggi:
class Producer implements Runnable { int i; String name; String [] messages; Producer (String name, int i) { this.name = name; this.i = 0; messages = new String[i]; } public void run() { try { while (true) { produceMessage(); Thread.sleep(1000); } } catch(InterruptedException e) {} } private synchronized void produceMessage() throws InterruptedException { while (i >= messages.length) { wait(); } messages[i]=new java.util.Date().toString(); System.out.println("Produce "+messages[i]); i++; notify(); } public synchronized String getMessage() throws InterruptedException { notify(); while (i == 0) { wait(); } String res=messages[i-1]; i--; return res; } }Adesso il consumatore può avere messaggi del produttore usando il metodo getMessage:
class Consumer implements Runnable { String name; Producer pr; Consumer (String name, Producer pr) { this.name = name; this.pr = pr; } public void run() { try { while (true) { System.out.println(name+" got "+pr.getMessage()); Thread.sleep(2000); } } catch(InterruptedException e) {} } }Si può mettere un produttore con un consumatore:
class Test { static public void main(String [] args) { Producer pr = new Producer("A",20); Thread th1 = new Thread(pr); Thread th2 = new Thread(new Consumer("B",pr)); th1.start(); th2.start(); } }Con il programma precedente si può anche avere più di un consumatore:
class Test { static public void main(String [] args) { Producer pr = new Producer("A",20); Thread th1 = new Thread(pr); Thread th2 = new Thread(new Consumer("B",pr)); Thread th3 = new Thread(new Consumer("C",pr)); th1.start(); th2.start(); th3.start(); } }In questo caso, c'è una competizione fra B e C per consumare. Questo significa che più di un oggetto può essere in uno stato wait. Con notify se ne risveglia solo uno. Il metodo notifyAll() permette di svegliare tutti. Una versione più completa dell'esempio precedente è la seguente:
class Producer implements Runnable { ... private synchronized void produceMessage() throws InterruptedException { while (i >= messages.length) { wait(); } messages[i]=new java.util.Date().toString(); System.out.println("Produce "+messages[i]); i++; notifyAll(); } public synchronized String getMessage() throws InterruptedException notify(); while (i == 0) { wait(); } String res=messages[i-1]; i--; return res; } }