Marco Ronchetti Java J0 1 Threads & Sincronizzazione Threads - cenni di base Una classe può essere threaded se: Marco Ronchetti è sottoclasse di Thread oppure implementa l’interfaccia Runnable. In entrambi i casi DEVE implementare il metodo run che viene eseguito quando la thread parte. J0 2 Threads - cenni di base Marco Ronchetti Public class C extends Thread { public void run() { //implementazione di run } } J0 3 Nel codice di una Classe che usa C: … C athread = new C(); athread.start(); // attiva il metodo run boolean f=athread.isAlive(); after start before end Threads - cenni di base Marco Ronchetti Public class C implements Runnable { public void run() { //implementazione di run } } Nel codice di una Classe che usa C: … C aRunnableClass = new C(); Thread athread = new Thread(aRunnableClass); athread.start(); // attiva il metodo run boolean f=athread.isAlive(); after start before end J0 4 Marco Ronchetti Metodi principali di Thread J0 5 Thread athread athread.start() // attiva il metodo run athread.join(anotherThread) //attende la fine di anotherThread … Thread.yield(); //cede la CPU (sospensione non temporizzata) Thread.sleep(long milliseconds); // sospensione temporizzata Thread.sleep(long milliseconds, int nanoseconds); athread.setDaemon(); athread.isDaemon() //analogo ai demoni unix METODI DEPRECATI! athread.stop(); // interrompe il metodo run (termina) athread.suspend(); // sospende indefinitamente il metodo run athread.resume(); // riprende dopo una suspend athread.destroy(); // distrugge la Thread Daemon Threads Marco Ronchetti Ci sono due tipi di threads: user threads e daemon threads. La differenza sta nel fatto che l’applicazione/applet termina quando non ci sono piu’ user threads. Le daemon threads servono dunque per effettuare servizi a favore delle user threads. Tutte le threads di sistema sono daemon threads. void setDaemon(boolean on) boolean isDaemon () J0 6 import java.awt.Graphics; import java.util.Date; Applet methods - 3 public class ShowAppletMethods extends java.applet.Applet Date d; static int count=0; simpleThread t; { class simpleThread extends Thread { ShowAppletMethods a; int seconds; simpleThread(ShowAppletMethods a, int s) { this.a=a; seconds=s*1000; } Marco Ronchetti public void init() { System.out.println((count++)+" Init Executed"); int seconds=Integer.parseInt(this.getParameter( "Secondi")); t=new simpleThread(this,seconds); t.start(); } public void start() { public void run() { System.out.println((count++)+" Start Executed"); while (true) { } try { public void stop() { sleep(seconds); System.out.println((count++)+" Stop Executed"); } } catch (InterruptedException e) {} public void destroy() { System.out.println("==> THREAD: System.out.println((count++)+" Destroy Executed"); calling repaint"); } a.repaint(); public void paint(Graphics g) { } System.out.println((count++)+" Paint Executed"); } d=new Date(); } g.drawString("Time now is "+d.getHours() +":" +d.getMinutes() +":" +d.getSeconds(), 5, 25); J0 } 7} Marco Ronchetti Perche’ Threads ? Nonblocking I/O tecniche standard non disponibili in Java: I/O multiplexing Polling Signals Alarms & Timers Support for (logically) parallel operations J0 8 Controllo delle Threads: rendez-vous Marco Ronchetti Class T extends Thread; T t1 = new T(); T t2 = new T(); t2.join(); // nel codice di t1 sospende l’esecuzione di t1 fino al completamento di t2 ovvero fino a quando t2 sara’ considerata “non attiva” void join(); void join(long m); //sospendi, ma per non piu’ di m milliseconds void join(long m, int n); //sospendi, ma per non piu’ di // m milliseconds e n nanoseconds J0 9 Behind the scenes Group Description System Clock handler gestisce sleep e wait(timeout) Idle thread attiva il garbage collector Garbage Collector libera gli oggetti non usati Finalizer Thread gestisce il metodo finalize() sugli oggetti liberati Main Main Marco Ronchetti J0 10 Name esegue il metodo main() dell’applicazione Behind the scenes - Applets Group Marco Ronchetti System Name ---- (come per le applicazioni) ----- AppletViewer AWT-Input AWT-Toolkit Screen updater Image fetcher Audio player AppletName AppletName J0 11 Description gestisce l’input dal windowing system nativo tratta gli eventi del win. sys. nativo gestisce la coda di repaint ed esegue paint carica immagini della rete (4 threads) gestisce l’output sonoro Applet Thread (esegue start(0, stop() ecc.) Marco Ronchetti Behind the scenes - Applets J0 12 In una applet che gestisca immagini e suono, ci sono almeno 12 threads attive oltre a quella propria della Applet! Marco Ronchetti Sezione critica t1 int k=c.incr(); int n = count; count= n + 1; return n; t2 int k=c.incr(); int n = count; count= n + 1; return n; int k=c.incr(); int n = count; int k=c.incr(); int n = count; count= n + 1; return n; count= n + 1; return n; J0 13 package counter; public class CounterUser { import java.io.*; public static void main(String arg[]) { class Counter { Counter cont=new Counter(); private int count = 0; T t1 = new T(cont); int maxiter=233; T t2 = new T(cont); public int incr() { t1.start();A int n = count; t2.start(); delay(maxiter); count } count= n + 1; 0 } return n; 0 } 1 public void delay(int maxiter) { 1 double z=1; 1 for (int k=0;k<maxiter;k++) { z = Math.sqrt(Math.sin(Math.random()*z)); 1 } 2 class T extends Thread { } 2 Counter c; } 2 T(Counter c) { this.c=c; 2 } 2 public void run() { 2 int k=0; 3 while (true) { 3 k=c.incr(); 3 System.out.println(this.toString()+" - "+k); c.delay(20*c.maxiter); 3 } } } Sezione critica Marco Ronchetti t1 int k=c.incr(); int n = count; count= n + 1; return n; t2 count int k=c.incr(); int n = count; count= n + 1; return n; int k=c.incr(); int n = count; 0 0 1 1 1 1 2 2 2 2 int k=c.incr(); int n = count; count= n + 1; return n; count= n + 1; return n; J0 14 3 3 package counter; import java.io.*; class Counter { private int count = 0; int maxiter=233; public syncronized int incr() { int n = count; delay(maxiter); count= n + 1; return n; } public void delay(int maxiter) { double z=1; for (int k=0;k<maxiter;k++) { z = Math.sqrt(Math.sin(Math.random()*z)); } } } Marco Ronchetti Concorrenza: metodi sincronizzati Una sola thread alla volta puo’ eseguire un metodo synchronized su un dato oggetto (su oggetti diversi sono possibili esecuzioni “parallele” di metodi synchronized!) Concetto di MONITOR (Analogia: bastone della staffetta) OGNI OGGETTO POSSIEDE UN MONITOR! J0 15 (ma anche le Classi possono possedere un monitor, per i metodi “static”) class A { public void doSomething(Object o) { … synchronized(o) { sezione critica di A } ... } class B { public void doSomethingElse(Object k) { … synchronized(k) { sezione critica di B } ... } … Point o1 = new Point(); A a1=new A(); A a2=new A(); B b1=new B(); B b2=new B(); Concorrenza: blocchi sincronizzati Marco Ronchetti A granularita’ piu’ bassa, e’ possibile sincronizzare blocchi di codice su di un monitor. J0 16 Nell’esempio a fianco, le sezioni critica di a1,a2,b2 NON possono essere eseguite parallelamente (sono sincronizzate tramite il monitor dell’oggetto o1. La sezione critica di b1 invece puo’ essere eseguita in parallelo a quelle di a1,a2,b2 perche’ si sincronizza con un diverso monitor. da thread diverse, vengono invocati: a1.doSomething(o1); b1.doSomethingElse(this); a2.doSomething(o1); b2.doSomethingElse(o1); Marco Ronchetti Chiamate sincronizzate ricorsive Class A { synchronized void m1() { … m2() … } synchronized void m2() { … } ... Questo codice NON da’ origine a deadlock! La thread PUO’ eseguire m2 perche’ GIA’ POSSIEDE il monitor relativo. (Un contatore tiene traccia di quanti accessi sincronizzati allo stesso monitor vengono fatti). J0 17 Marco Ronchetti Soluzioni per il problema della concorrenza Soluzioni classiche: Monitors + condition variables (Hoare-Hansen 74/75) Monitor: una collezione di procedure, variabili e strutture dati raccolte in uno speciale modulo, con la proprietà che un solo processo alla volta può eccedere ad un monitor. C.V.: Variabili con due primitive: wait e notify. wait mette il processo in attesa sulla c.v. notify risveglia un processo in attesa sulla c.v. J0 18 Marco Ronchetti Concorrenza: wait & notify synchronized (pippo) { while (! condizione) { pippo.wait(); } } synchronized (pippo) { pippo.notify(); // risveglia il primo in coda su pippo } wait(long msec) wait(long msec, int nsec) J0 19 ... esegue una wait con timeout esegue una wait con timeout notifyAll() risveglia tutte le threads in attesa sullo stesso monitor (usato ad esempio in MediaTracker) public class PingPong { //Main method class Ping extends Thread { PingPong() { Object monitor1,monitor2; Object z1=new Object(); Ping(Object o1,Object o2) { Object z2=new Object(); this.monitor1=o1; this.monitor2=o2; Ping t1=new Ping(z1,z2); } Pong t2=new Pong(z1,z2); public void run() { t1.start(); t2.start(); while (true) { try { try { class Pong extends Thread { Thread.sleep(60*1000); Thread.sleep(1000); Object monitor1,monitor2; } catch (InterruptedException i) {} } catch (InterruptedException i) {} Pong(Object o1,Object o2) { t1.stop(); t2.stop(); System.out.println(">> Ping"); this.monitor1=o1; } synchronized(monitor1) { this.monitor2=o2; public static void main System.out.println(">> == Ping is notifying"); } (String[] args) { monitor1.notify(); public void run() { new PingPong (); } while (true) { } synchronized(monitor2) { synchronized(monitor1) { } try{ try{ System.out.println(">> == Ping is waiting"); System.out.println("<< == Pong is waiting"); monitor2.wait(); monitor1.wait(); } catch (InterruptedException e) {} } catch (InterruptedException e) {} } } } System.out.println("<< Pong"); } synchronized(monitor2) { } System.out.println("<< == Pong is notifying"); monitor2.notify(); } J0 } 20 } } Marco Ronchetti wait & notify wait & notify Marco Ronchetti Attenzione alla perdita di messaggi! J0 21 Cosa avviene se la notify() viene inviata prima della corrispondente wait() ? Concorrenza: wait & notify Marco Ronchetti NOTE su wait e notify: wait() rilascia temporaneamente il monitor J0 22 Non c’e’ modo di distinguere se da una wait si e’ ... usciti per timeout o per notification esecuzioni di wait o notify senza acquisizione del monitor danno origine a IllegalMonitorStateException Produttore e consumatore con buffer Marco Ronchetti Problema classico: J0 23 Due processi (Produttore e Consumatore) collaborano, scambiandosi dati tramite un buffer condiviso. Occorre evitare che : - il Produttore tenti di scrivere se il buffer e’ pieno, - il Consumatore tenti di leggere se il buffer e’ vuoto. Produttore e Consumatore package ProCon; public static void main(String[] args) { try { ProCon p = new ProCon(); } catch (Exception w) {} } Marco Ronchetti public class ProCon { J0 24 ProCon() throws InterruptedException { Buffer b = new Buffer(10); Consumer c = new Consumer(b,600); Producer p = new Producer(b,300); Thread tc = new Thread(c); Thread pc = new Thread(p); tc.start(); pc.start(); pc.join(); tc.join(); System.out.println("ProCon resuming execution..."); synchronized (this) { wait(3000); } } } Marco Ronchetti Produttore e Consumatore J0 25 package ProCon; public class Buffer { private char[] buf; // buffer storage private int last; // last occupied position public Buffer(int sz) { buf = new char[sz]; last = 0; } public boolean isFull() { return (last == buf.length); } public boolean isEmpty() { return (last == 0); } public synchronized void put(char c) { while(isFull()) { // wait for room to put stuff try { wait(); } catch(InterruptedException e) { } } public synchronized char get() { buf[last++] = c; while(isEmpty()) { // wait for stuff to read notify(); try { wait(); } catch(InterruptedException e) { } } } char c = buf[0]; System.arraycopy(buf, 1, buf, 0, --last); notify(); return c; } } Produttore e Consumatore Marco Ronchetti package ProCon; public class Producer implements Runnable { private Buffer buffer; private int delay=0; J0 26 } package ProCon; public class Consumer implements Runnable { private Buffer buffer; private int delay=0; public Producer(Buffer b, int d) { buffer = b; delay = d; } public void run() { for (int i=0; i<52; i++) { char c = (char)('A' + (i%26)); System.out.println("Producing "+c); buffer.put(c); // write to the buffer try { Thread.sleep(delay); } catch (InterruptedException ie){} } System.out.println("Producer Ended"); } public Consumer(Buffer b, int d) { buffer = b; delay = d; } public void run() { for (int i=0; i<52; i++) { System.out.println("Consuming "+buffer.get()); try { Thread.sleep(delay); } catch (InterruptedException ie){} } System.out.println("Consumer Ended"); } } Semafori con i monitor Marco Ronchetti public class Semaphore { private int S=0; private int waitingProcesses=0; J0 27 Semaphore(int v) { S=v; } public synchronized void down() { if (S>0) S--; else { try { waitingProcesses++; wait(); } catch (InterruptedException e) {} } public synchronized void up() { } if (waitingProcesses == 0) S++; else { waitingProcesses --; notify(); } } } Produttore e Consumatore coi semafori Marco Ronchetti public class Buffer { private char[] buf; // buffer storage public class ProCon { Le classi private int last; // last occupied position Semaphore empty; producer e ProCon() throws InterruptedException { Semaphore full; consumer Semaphore mutex; Buffer b = new Buffer(10); non cambiano Consumer c = new Consumer(b,600); public Buffer(int sz) { Producer p = new Producer(b,300); public void put(char c) { buf = new char[sz]; Thread tc = new Thread(c); empty.down(); last = 0; Thread pc = new Thread(p); mutex.down(); empty=new Semaphore(sz); tc.start(); buf[last++] = c; full=new Semaphore(0); pc.start(); mutex.up(); mutex=new Semaphore(1); pc.join(); full.up(); } tc.join(); } System.out.println("ProCon resuming execution..."); synchronized (this) { public char get() { wait(3000); full.down(); } mutex.down(); } char c = buf[0]; public static void main(String[] args) { System.arraycopy(buf, 1, buf, 0, --last); try { mutex.up(); ProCon p = new ProCon(); empty.up(); } catch (Exception w) {} return c; J0 } } 28 } } Marco Ronchetti Threads scheduling Lo scheduling della JVM e’ “inaffidabile” (perché ancora indefinito), nel senso che cambiando piattaforma cambia implementazione. Se serve uno scheduling con proprietà ben definite e’ bene scrivere il proprio scheduler. Per dettagli, vedere Oaks and Wong, “Java Threads”, O’Reilly 1997 J0 29 A simple Round Robin scheduler public class SimpleScheduler extends Thread { int timeslice; Marco Ronchetti public SimpleScheduler(int t) { timeslice=t; setPriority(Thread.MAX_PRIORITY); setDaemon(true); } public void run() { while (true) try { sleep(timeslice); } catch (Exception e) {} } } J0 30 class TestThread extends Thread { public void run() { ... } } public class Test { public static void main (string args[]) { new SimpleScheduler(100).start(); TestThread t1,t2,t3; t1=new TestThread(); t2=new TestThread(); t3=new TestThread(); t1.start(); t2.start(); t3.start(); } } Marco Ronchetti Concorrenza: wait & notify Si rimanda a “Java Threads” Di Oaks e Wong Ed. O’Reilly “Java Restaurant” Ed. Apogeo J0 31