Summary: A brief introduction to Java Threads as accessible from Processing.
stampa() sull'oggetto cl
della classe Classe1.
class Classe1 {
void stampa() {
for (int i=0; i< 100; i++) println("yep");
}
}
void setup() {
Classe1 cl = new Classe1();
cl.stampa();
}
Il flusso di elaborazione si può rappresentare graficamente
con il diagramma di attivazione di Figura 1, il
quale presenta evidentemente un singolo thread.
![]() Figura 1: Diagramma di attivazione a thread singolo |
Thread, allora è possibile procedere
all'attivazione di un thread secondario mediante invocazione
del metodo start(). Il codice va riscritto come
class Classe2 extends Thread{
void run() {
for (int i=0; i< 100; i++) println("yep");
}
}
void setup() {
Classe2 cl = new Classe2();
cl.start();
}
Si noti che il metodo stampa() ora si chiama
run(). Il metodo start() esiste
nelle superclassi di Classe2 (nella classe
Thread, e si occupa dell'invocazione del metodo
run(). Questa volta il flusso di elaborazione,
riportato in Figura 2 presenta due thread.
![]() Figura 2: Diagramma di attivazione a doppio thread |
Runnable di
Java. E' questa modalità che bisogna usare se si vuole
attivare un nuovo thread su un oggetto di una classe che è
già dichiarata come estensione di un'altra classe.
Thread possiede un metodo
sleep() che la mette in stato
blocked per un certo numero di
millisecondi. Ciò consente di programmare in maniera
compatta e agevole flussi di eventi tra loro
indipendenti. Ad esempio, il codice che segue produce il
disegno di ellissi di due colori diversi, e ogni colore
corrisponde ad un diverso intervallo di tempo (i.e., 1000 e
1300 millisecondi) tra due eventi successivi di disegno.
class TimerThread extends Thread{
int timediff; // quanto temporale
color col;
TimerThread(color c, int td) {
timediff = td;
col = c;
}
void run() {
while(true) {
fill(col);
try {
ellipse(int(random(100)), int(random(100)),
int(random(20)), int(random(20)));
sleep(timediff);
} catch (Exception e) {println("Exception in sleep");}
}
}
}
void setup() {
TimerThread tt1 = new TimerThread(color(120,120,0),1000);
TimerThread tt2 = new TimerThread(color(0,120,120),1300);
tt1.start();
tt2.start();
}
void draw() {
}
Si nota che il metodo sleep() di Java esige di
essere invocato all'interno di un costrutto try
catch(), cioè di una sezione di codice che consenta la
cattura delle eccezioni. La gestione
delle eccezioni consente di affrontare delle condizioni
che alterano il normale flusso di esecuzione di un
programma. Nell'esempio, la sleep() può fallire e
sollevare (throw) una eccezione che, in questo
caso, è gestita mediante la mera scrittura di una messaggio
sulla console.
StringBuffer stdin;
boolean linea;
color colore;
void keyReleased() {
char c = key;
if (c!='\n') {
stdin.append(c);
}
else linea=true;
}
class ColorInput extends Thread {
String results;
char c;
void run() {
while(true) {
if (linea) {
println(stdin);
results=stdin.toString();
stdin.setLength(0);
linea = false;
if (results.equals("rosso")) {
colore = color(255, 0, 0);
}
if (results.equals("verde")) {
colore = color(0, 255, 0);
}
if (results.equals("blu")) {
colore = color(0, 0, 255);
}
}
try {
sleep(5); // to relief the cpu from active waiting
} catch (Exception e) {println("Exception in sleep");}
}
}
}
class TimerThread extends Thread{
int timediff; // quanto temporale
TimerThread(int td) {
timediff = td;
}
void run() {
while(true) {
try {
fill(colore);
ellipse(int(random(100)), int(random(100)),
int(random(20)), int(random(20)));
sleep(timediff);
} catch (Exception e) {println("Exception in sleep");}
}
}
}
void setup() {
stdin = new StringBuffer();
TimerThread tt1 = new TimerThread(100);
ColorInput ci = new ColorInput();
ci.start();
tt1.start();
}
void draw() {
}
Sono presenti, in questo caso, due diverse estensioni della
classe Thread. La prima estensione fa una
attesa attiva di linee di testo, impostando il colore
ogniqualvolta viene rilevata una linea di testo contenente
una delle tre parole chiave riconosciute. L'invocazione di
sleep(5) rende questa attesa attiva meno
onerosa per la CPU. L'altro thread, invece, si occupa di
disegnare un'ellisse al secondo.
In Processing è difficile realizzare un input bloccante da
tastiera, in quanto non è accessibile direttamente lo stream
System.in, sul quale in Java si può normalmente
applicare lettura bufferizzata bloccante. keyReleased(), keyPressed(), e
keyTyped(). E' possibile però fare un input
bloccante di una linea da file di testo con un codice del
tipo
try {
BufferedReader stdiin = createReader("nomefile");
println(stdiin.readLine());
}catch(Exception e){}
dove createReader() è una funzione di Processing che crea un BufferedReader object da un file o da una URL. Essa consente una leggera semplificazione rispetto al codice Java
try {
FileReader is = new FileReader("nomefile");
BufferedReader stdiin = new BufferedReader( is );
println(stdiin.readLine());
}catch(Exception e){}
Per semplificare la lettura da file di testo, locali o
remoti, Processing mette a disposizione la funzione
loadStrings(), che carica tutte le linee di
file di testo in un array di tipo
String[]. Questo metodo può essere utile se il
file non è troppo grande o dinamicamente variabile.
class sentenceReader extends Thread{
int timediff; // quanto temporale
sentenceReader(int td) {
timediff = td;
}
void run() {
while(true) {
try {
BufferedReader stdiin = createReader("http://www.essl.at/cgi-bin/swrap/cgis/lexikon-orakel.pl");
for (int i=0; i<13; i++) stdiin.readLine();
println(stdiin.readLine());
sleep(timediff);
}catch(Exception e){}
}
}
}
void setup() {
sentenceReader st = new sentenceReader(2000);
st.start();
}
void draw() {
}
stdin. I comandi possono essere
consumati dall'uno o dall'altro dei thread, ma uno stesso
comando non può essere consumato da entrambi. In questo caso
si possono presentare problemi di race
condition, cioè configurazioni di codice concorrente
che danno luogo a risultati dipendenti dalla sequenza di
scheduling attribuita ai vari
thread. In particolare, la sezione
critica
results=stdin.toString();
stdin.setLength(0);
linea = false;
può dare luogo a comportamenti inconsistenti se interrotta
dallo scheduler per passare il controllo
dall'uno all'altro thread. Questi problemi si possono
risolvere imponendo la non interrompibilità delle sezioni
critiche di codice, per mezzo della parola chiave
synchronized.
Nella fattispecie, si può introdurre una classe con metodi sincronizzati:
class IO extends Thread {
String results;
synchronized String acquire() {
results=stdin.toString();
linea = false;
stdin.setLength(0);
return(results);
}
}
I metodi invocati su un oggetto di questa classe non sono
interrompibili da metodi invocati sullo stesso oggetto. In ambito di
transazioni bancarie, per esempio, un metodo prelievo()
dovrà essere sincronizzato per evitare inconsistenze in presenza di
prelievi multipli, come possono essere effettuati da co-titolari dello
stesso conto. La sincronizzazione di thread è un argomento complesso,
per l'approfondimento del quale esistono libri
specializzati.
stdin.
getName() per stampare il nome del thread
interessato.
StringBuffer stdin;
boolean linea;
color colore;
IO io = new IO();
void keyReleased() {
char c = key;
if (c!='\n') {
stdin.append(c);
}
else linea=true;
}
class IO extends Thread {
String results;
synchronized String acquire() {
results=stdin.toString();
linea = false;
stdin.setLength(0);
return(results);
}
}
class ColorInput extends Thread {
String results;
char c;
void ColorInput (IO inout) {
io = inout;
}
void run() {
while(true) {
if (linea) {
results=io.acquire();
println(this.getName() + results);
if (results.equals("rosso")) {
colore = color(255, 0, 0);
}
if (results.equals("verde")) {
colore = color(0, 255, 0);
}
if (results.equals("blu")) {
colore = color(0, 0, 255);
}
}
try {
sleep(2); // to relief the cpu from active waiting
} catch (Exception e) {println("Exception in sleep");}
}
}
}
class TimerThread extends Thread{
int timediff; // quanto temporale
TimerThread(int td) {
timediff = td;
}
void run() {
while(true) {
try {
fill(colore);
ellipse(int(random(100)), int(random(100)),
int(random(20)), int(random(20)));
sleep(timediff);
} catch (Exception e) {println("Exception in sleep");}
}
}
}
void setup() {
stdin = new StringBuffer();
TimerThread tt1 = new TimerThread(100);
ColorInput ci = new ColorInput();
ColorInput ci2 = new ColorInput();
ci.start(); ci2.start();
tt1.start();
}
void draw() {
}
Comments, questions, feedback, criticisms?