Sincronizzazione in Java - sistemisds.altervista.org

annuncio pubblicitario
Sincronizzazione in Java
La potenza del multithreading risiede nel fatto che parti di uno stesso programma girano in modo
indipendente uno dall’altro, ma a volte è necessario che certe operazioni vengano eseguite
serialmente , vale a dire da un solo thread alla volta. Se per esempio, due o più thread accedono
contemporaneamente a un set di variabili comuni oppure a una stessa risorsa del sistema, come un
file o una connessione di rete, i risultati possono essere imprevedibili.
Dobbiamo avere quindi a disposizione degli strumenti che permettano di eseguire certe
sezioni di codice a non più di un thread alla volta. In altre parole è necessario sincronizzare i thread.
Questo non è un problema nuovo, né tanto meno è caratteristico di Java. Per affrontare il
problema della sincronizzazione dei thread si può ricorrere al concetto di mutex. Un mutex
(contrazione di mutual exclusion) è una risorsa del sistema che può essere posseduta da un solo
thread alla volta. Per fare un paragone pratico, un mutex è un po’ come la chiave di una toilette che
in alcuni esercizi, viene tenuta alla cassa. Se una seconda persona richiede la chiave mentre la
toilette è ancora occupata, deve attendere che la chiave sia restituita alla cassa prima di poterla
ottenere.
In Java tale meccanismo è stato automatizzato quanto più possibile; ogni istanza di qualsiasi
oggetto ha associato un mutex. Quando un thread esegue un metodo che è stato dichiarato
sincronizzato mediante l’identificatore synchronized, entra in possesso del mutex associato
all’istanza e nessun altro metodo sincronizzato può essere eseguito su quell’istanza fintanto che il
thread non ha terminato l’esecuzione del metodo.
public class ProvaThread2 implements Runnable
{
public static void main (String argv[] ) {
ProvaThread2 pt = new ProvaThread2();
Thread t =new Thread (pt);
t.start();
pt.m2();
}
public void run() {
m1();
}
synchronized void m1() {
for ( char c= ‘A’ ; c < ‘F’ ; c++) {
System.out.println(c);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
void m2 () {
for ( char c= ‘1’ ; c < ‘6’ ; c++) {
System.out.println(c);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
}
Nell’esempio due metodi m1 e m2, vengono invocati da due thread su una stessa istanza pt. Uno dei
due metodi è stato dichiarato synchronized mentre l’altro, m2, no. Dunque il mutex associato a pt
viene acquisito dal metodo m1, ma non blocca l’esecuzione di m2 perché esso non tenta di acquisire
il mutex. Il risultato prodotto è il seguente:
1
A
2
B
3
C
4
D
5
E
Se invece si dichiara synchronized anche il metodo m2, si hanno due thread che tentano di acquisire
lo stesso mutex, per cui i due metodi vengono eseguiti in mutua esclusione prima uno e dopo
l’altro:
1
2
3
4
5
A
B
C
D
E
Comunque, nel caso sia noto il fatto che una classe non ha metodi sincronizzati ma si desideri
evitare l’accesso contemporaneo a uno o più metodi, è possibile acquisire il mutex di una
determinata istanza racchiudendo, il o i, metodi da sincronizzare in un blocco synchronized la cui
forma generale è la seguente:
synchronized (istanza) {
istruzione1;
…
istruzioneN;
}
public class ProvaThread5 implements Runnable
{
public static void main (String argv[] ) {
ProvaThread5 pt = new ProvaThread5();
Thread t =new Thread (pt);
t.start();
synchronized (pt) {
pt.m2();
}
}
public void run() {
m1();
}
synchronized void m1() {
for ( char c= ‘A’ ; c < ‘F’ ; c++) {
System.out.println(c);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
void m2 () {
for ( char c= ‘1’ ; c < ‘6’ ; c++) {
System.out.println(c);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {}
}
}
}
Esegue i metodi m1 e m2 non contemporaneamente anche se m2 non è dichiarato synchronized.
Questo metodo di gestire la sincronizzazione con dei mutex impliciti semplifica notevolmente la
gestione di programmi multithread, poiché il programmatore non ha la preoccupazione di rilasciare
il mutex ogni volta che un metodo termina normalmente o a causa di una eccezione, dato che questa
operazione viene eseguita automaticamente.
Comunicazione tra thread
Per sfruttare meglio la potenzialità del multithreading è necessario disporre di meccanismi di
sincronizzazione che permettano la comunicazione tra thread in modo che essi possano collaborare
per uno scopo comune. Un esempio classico è quello di un thread che produce dei dati e di un altro
che li deve utilizzare in qualche modo, per esempio inviarli ad una stampante. Quando due thread
cooperano in questo modo si dice che fra essi esiste una relazione produttore-consumatore.
Java mette a disposizione alcuni metodi che sono disponibili da istanze di qualsiasi classe. Tali
metodi devono essere invocati solo in metodi sincronizzati:
 public final void wait(): il thread che invoca questo metodo rilascia il mutex associato
all’istanza e viene sospeso fintanto che non viene risvegliato da un altro thread che
acquisisce lo stesso mutex e invoca il metodo notify o notifyAll.
 public final void noyify(): risveglia il primo thread che ha invocato wait sull’istanza. Poiché
il metodo che invoca notify deve aver acquisito il mutex, il thread risvegliato, deve
attendere il rilascio, dopodiché compete per la sua acquisizione come un qualsiasi altro
thread.

public final void notifyAll: risveglia tutti i thread che hanno invocato wait sull’istanza.
In pratica un thread esegue il metodo wait() quando aspetta che si verifichi una condizione o evento
per effetto di un altro thread; l’altro thread , quando si verifica la condizione, notifica l’evento con il
metodo notify().
Il metodo wait() rilascia il mutex o lock sul monitor e sospende il thread. Un thread che ha eseguito
il metodo wait() riacquista il mutex o lock del monitor quando viene fatto ripartire dopo una
notify().
Il metodo wait() si usa inserendolo in un ciclo che controlla il verificarsi di una certa condizione:
synchronized void attesaCondizione()
{
while(!condizione)
wait();
istruzioni da eseguire
}
Implementazione di un semaforo in java
// si utilizza la classe semaforo
// per assicurare la mutua esclusione
// nell'utilizzo di attrezzi da parte di due meccanici
public class Meccanici {
public static void main(String args[]){
Semaforo attrezzo1 = new Semaforo(1);
Semaforo attrezzo2 = new Semaforo(1);
Meccanico m1=new Meccanico(attrezzo1,attrezzo2,1);
Meccanico m2=new Meccanico(attrezzo1,attrezzo2,2);
m1.start();
m2.start();
} //end main
}
class Meccanico extends Thread {
Semaforo a1;
Semaforo a2;
int m;
public Meccanico(Semaforo s1,Semaforo s2,int n){
a1=s1;
a2=s2;
m=n;
}
public void run() {
int k;
a1.P();
for(k=1;k<10;k++)
{
System.out.println("meccanico "+m+" con attrezzo 1");
a1.V();
System.out.println("attrezzo 1 libero");
try {
Thread.sleep(500);
} catch(Exception e) { };
a2.P();
System.out.println("meccanico "+m+" con attrezzo 2");
a2.V();
System.out.println("attrezzo 2 libero");
try {
Thread.sleep(500);
} catch(Exception e) { };
a1.P();
System.out.println("Meccanico"+m+": mi serve anche l'attrezzo 2");
try {
Thread.sleep(500);
} catch(Exception e) { };
a2.P();
System.out.println("meccanico "+m+" con attrezzi 1 e 2");
a2.V();
a1.V();
System.out.println("attrezzi liberi");
try {
Thread.sleep(500);
} catch(Exception e) { };
} //fine for
} //fine run
}
class Semaforo {
int valore;
public Semaforo(int v){
valore=v;
}
synchronized public void P(){
while (valore==0){
try { wait();
}
catch(InterruptedException e){}
} valore--;
} //end P
synchronized public void V(){
valore++;
notify();
} //end V
}
Esempio di interazione diretta ( produttore – consumatore)
public class ProdCons {
public static void main(String args[]){
Semaforo mes = new Semaforo(0);
Semaforo lib = new Semaforo(5);
Buffer buf = new Buffer();
Produttore pr=new Produttore(mes,lib,buf);
Consumatore co=new Consumatore(mes,lib,buf);
pr.start();
co.start();
} //end main
}
class Produttore extends Thread {
Semaforo messaggi;
Semaforo liberi;
Buffer bb;
int m;
public Produttore(Semaforo s1,Semaforo s2,Buffer b){
messaggi=s1;
liberi=s2;
bb=b;
}
public void run() {
int k;
for (k=0;k<10;k++){
liberi.P();
bb.scrivi("messaggio"+k);
messaggi.V();
try { Thread.sleep(500);
}catch (Exception e){
}
}
} //fine run
}
class Consumatore extends Thread {
Semaforo messaggi;
Semaforo liberi;
Buffer bb;
int m;
public Consumatore(Semaforo s1,Semaforo s2,Buffer b){
messaggi=s1;
liberi=s2;
bb=b;
}
public void run() {
while (true){
messaggi.P();
String lettura=bb.leggi();
System.out.println(lettura);
liberi.V();
}
} //fine run
}
class Buffer {
String memoria[] = new String[5];
int w,l;
public Buffer() {
w=0;
l=0;
}
public synchronized void scrivi(String messaggio){
memoria[w]=messaggio;
w=(w+1)%5;
}
public synchronized String leggi(){
String temp;
temp=memoria[l];
l=(l+1)%5;
return temp;
}
} //fine Buffer
/*
class Semaforo {
int valore;
public Semaforo(int v){
valore=v;
}
synchronized public void P(){
while (valore==0){
try { wait();
}
catch(InterruptedException e){}
} valore--;
} //end P
synchronized public void V(){
valore++;
notify();
} //end V
} */
Scarica