Concorrenza e Thread

annuncio pubblicitario
Programmazione
concorrenteinJava
damateriale di
CarloGhezzi eAlfredoMotta
Parallelismo =“multitasking”
• Possiamoscrivereunprogrammaincuidiverse
attività(task)evolvonoinparallelodaunpuntodi
vistafisicoologico
• Massimoparallelismofisico
– Ogniattivitàparallelahaadisposizioneunprocessore
fisico
• Altrimentivengonoeseguitedaprocessori
condivi
– Secondomodalitàdecisedaunoscheduler,in
generalenoncontrollabiledalprogrammatore
Multitaskingsusingoloprocessore
• Approccio
– Processoreesegueuntask
– Passavelocementeaunaltro
– Sottoilgovernodelloscheduler
• Ilprocessoresembralavoraresuidiversitask
concorrentemente
• Passaggiodauntaskall’altroneimomentidi
inattivitàoperesaurimentodellafinestra
temporale(“timesharing”)
Caso 1:Tasksingolo
Caso 2:Duetask
4
Multitaskingalivellodiprocessi
• Processo
– Programmaeseguibilecaricatoinmemoria
– Haunsuospaziodiindirizzi(variabiliestrutture
datiinmemoria)
– Ogniprocessoesegueundiversoprogramma
– IprocessicomunicanoviaSO,file,rete
– Puòcontenerepiùthread
Multitaskingalivellodithread
• Threadèun’attivitàlogicasequenziale
• Unthreadcondividelospaziodiindirizzicon
glialtrithreaddelprocessoecomunicavia
variabilicondivise
• Haunsuocontestodiesecuzione(program
counter,variabililocali)
• Siparlaspessodiprocessolight-weight
ThreadinJava- metodo1
ClasseThread
start
avviailthread(eseguendoilmetodorun)
join
chiamatosuunthreadspecificoehaloscopodi
mettereinattesailthreadattualmentein
esecuzionefinoaquandoilthreadsucuièstato
invocatoilmetodojoin()nontermini
isAlive
controllaseilthreadèvivo(inesecuzione,inattesa
obloccato)
sleep(intms)
sospendel’esecuzionedelthread
yield
mettetemporaneamenteinpausailthread
correnteeconsenteadaltrithreadinstato
Runnable(qualoravenesiano)diavereunachance
peressereeseguiti
public class A extends Thread {
public void run() {
for (int i=0; i<=5; i++) System.out.println("From Thread A: i= "+i);
System.out.println("Exit from A");
}
}
public class B extends Thread {
public void run() {
for (int j=0; j<=5; j++) System.out.println("From Thread B: j= ""+j);
System.out.println("Exit from B");
}
}
public class C extends Thread {
public void run() {
for (int k=0; k<=5; k++) System.out.println("From Thread C: k= "+k);
System.out.println("Exit from C");
}
}
public class ThreadTest {
public static void main(String[] args) {
new A().start();
new B().start();
new C().start();
}
}
Outputpossibili
Caso1
From
From
From
From
From
From
Exit
From
From
From
From
From
From
Exit
From
From
From
From
From
From
Exit
Thread
Thread
Thread
Thread
Thread
Thread
from A
Thread
Thread
Thread
Thread
Thread
Thread
from B
Thread
Thread
Thread
Thread
Thread
Thread
from C
Caso2
A:
A:
A:
A:
A:
A:
i=
i=
i=
i=
i=
i=
0
1
2
3
4
5
B:
B:
B:
B:
B:
B:
j=
j=
j=
j=
j=
j=
0
1
2
3
4
5
C:
C:
C:
C:
C:
C:
k=
k=
k=
k=
k=
k=
0
1
2
3
4
5
From
From
From
From
From
From
Exit
From
From
From
From
From
From
From
From
From
From
From
From
Exit
Exit
Thread
Thread
Thread
Thread
Thread
Thread
from A
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
Thread
from B
from C
A:
A:
A:
A:
A:
A:
i=
i=
i=
i=
i=
i=
0
1
2
3
4
5
C:
C:
C:
C:
C:
C:
B:
B:
B:
B:
B:
B:
k=
k=
k=
k=
k=
k=
j=
j=
j=
j=
j=
j=
0
1
2
3
4
5
0
1
2
3
4
5
ThreadinJava– metodo2
• LaclasseThreadinrealtàimplementa
un’interfacciachiamataRunnable
• L’interfacciaRunnabledefinisceunsolo
metodorunchecontieneilcodicedelthread
• Suciòsibasaunmodoalternativo
ThreadinJava—metodo2
• metodopiùgeneralesiusasesideve
ereditaredaqualcheclasse
Daticondivisi
• Può essere necessario imporre che certe sequenze di
operazioni che accedono adati condivisi vengano
eseguite dai taskinmutua esclusione
class ContoCorrente {
private float saldo;
public ContoCorrente(float saldoIniz){
saldo=saldoInizi;
public void deposito(float soldi){
saldo += soldi;}
public void prelievo (float soldi){
saldo -=soldi;}
...
Interferenza
• Fenomenocausatodall’interleavingdioperazioni
didueopiùthread
– Letturadiy
– Esecuzioneespressione
– Scritturainx
• L’esecuzioneconcorrentedix+=yex-=y
puòavereunodeiseguentieffetti
– incrementarexdiy
– lasciareximmutata
– decrementarexdiy
Sequenze“atomiche”
• Generalizzazionedelproblema
– avoltesivuolechecertesequenzediistruzioni
venganoeseguiteinisolamento,senza
interleavingconistruzionidialtresequenze
parallelechealtrithreadpotrebberoeseguire
– siparladi“sequenzeatomiche”
Altroproblemadiconcorrenza
• Avolte,oltre avoler l’atomicità dicerte
sequenze,si vogliono imporre certi
ordinamenti nell’esecuzione dioperazioni
• Peresempio,che l’operazione Aeseguita da
unthreadvenga eseguita prima
dell’operazione Bdiunaltro thread
• Disolito ciò deriva dalfatto divoler garantire
certe proprietà diconsistenza dei dati
Comerendereimetodi"atomici"
• Laparolachiave"synchronized” applicataa
metodioblocchidicodice
class ContoCorrente {
private float saldo;
public ContoCorrente(float saldoIniz){
saldo = saldoIniz;}
public synchronized void deposito(float soldi){
saldo += soldi;}
public synchronized void prelievo(float soldi){
saldo -= soldi;}
...
Esempio
public class SynchronizedCounter {
private int c = 0;
}
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
public class TaskA extends Thread {
private SynchronizedCounter counter;
}
}
public TaskA(SynchronizedCounter c) {
counter = c;
public void run(){
counter.increment();
System.out.println(counter.value());
}
public class TaskB extends Thread {
private SynchronizedCounter counter;
public TaskB(SynchronizedCounter c) {
counter = c;
}
}
public void run(){
counter.decrement();
System.out.println(counter.value());
}
public class ThreadTest {
public static void main(String[] args) {
SynchronizedCounter c = new SynchronizedCounter();
}
}
TaskA ta = new TaskA(c);
TaskB tb = new TaskB(c);
ta.start();
tb.start();
Metodisynchronized
• Javaassocia unintrinsic lock aciascun oggetto
– Ilockoperano alivello dithread
• Quando il metodo synchronizedviene invocato
– senessun metodo synchronizedè inesecuzione,
l'oggetto viene bloccato (locked)equindi il metodo
viene eseguito
– sel'oggetto è bloccato,il taskchiamante viene
sospeso fino aquando il taskbloccante libera il lock
• Almassimounsingolothreadpuò trovarsi adeseguire
istruzioni all’interno diuno stesso metodo synchronized
Commentisullock
• Diverseinvocazioni dimetodi synchronizedsullo
stesso oggetto nonsono soggette ainterleaving
• Icostruttori nonpossono essere synchronized
– Soloil threadche crea l’oggetto deve avere accessoad
esso mentre viene creato
• Eventuali dati finalpossono essere letti con
metodi nonsynchronized
– Idati sono comunque insolalettura enonpossono
essere modificati
Ulterioricommentisullock
• L’intrinsiclockvieneacquisitoautomaticamente
all’invocazionedelmetodosynchronizede
rilasciatoalritorno(sianormalecheeccezionale
chedauncaughtexception)
• Seilmetodosynchronizedfossestatic
– Ilthreadacquisiscel’intrinsiclockperilClassobject
associatoallaclasse
– Pertantol’accessoaicampistaticècontrollatodaun
lockspeciale,diversodaquelliassociatialleistanze
dellaclasse
Synchronizedstatements
• Devonospecificarel’oggettoacuiapplicareillock
public void addName(String name) {
synchronized(this) {
lastName = name;
nameCount++;
}
nameList.add(name);
}
• Sirilasciaillockall’oggettoprimadiinvocareun
metodochepotrebbeasuavoltarichiederedi
attendereilrilasciodiunlock
Controllo“fine” dellaconcorrenza
• Esempio:classe conduecampi che nonvengono
modificati mai insieme
public class TestBlock {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {c1++;}
}
}
public void inc2() {
synchronized(lock2) {c2++;}
}
Alcuneregolepratiche
• Usarelock perlamodificadegliattributi
dell’oggetto
– Peresserecertidiavereunostatoconsistente
• Usarelock perl’accessoacampidell’oggetto
probabilmentemodificati
– Perevitaredileggerevalori“vecchi”
• Nonusarelock quandosiinvocaunmetodosu
altrioggetti
– Perevitaredeadlock
• Nonc’èbisognodilock peraccederealleparti
“stateless” diunmetodo
Liveness
• Èunaproprietàmoltoimportanteinpratica
• Significacheun’applicazioneconcorrente
vieneeseguitaentroaccettabililimitiditempo
• Lesituazionidaevitareattraversoun’attenta
progettazionesono
– deadlock,starvationelivelock
Deadlock
• Dueopiùthreadsonobloccatipersempre,in
attesal’unodell’altro
– Esempio:AnnaeGiacomosonoamiciecredono
nelgalateo,chedicecheseunapersonasiinchina
aunamico,deverestareinchinatafinoache
l’amicorestituiscel’inchino
• Problema:inchinoreciprocoallostessotempo
public class Friend {
private final String name;
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
public synchronized void bow(Friend bower) {
System.out.format("%s: %s" + " has bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
}
public synchronized void bowBack(Friend bower) {
System.out.format("%s: %s" + " has bowed back to me!%n",
this.name, bower.getName());
}
public class ThreadTest {
public static void main(String[] args) {
final Friend anna = new Friend("Anna");
final Friend giacomo = new Friend("Giacomo");
new Thread(new Runnable() {
public void run() {
anna.bow(giacomo);
}
}).start();
}
}
new Thread(new Runnable() {
public void run() {
giacomo.bow(anna);
}
}).start();
Classianonime:approcio
comodopercrearenuovi
threadconunsemplice
comportamento
Starvation
• Situazioneincuiunthreadhadifficoltàad
accedereaunarisorsacondivisaequindiha
difficoltàaprocedere
• Esempio:
– Task“greedy” chemoltofrequentemente
invocanometodilunghiritardanocostantementeil
thread
– Unoschedulercheusaprioritàcedesempre
precedenzaaitaskgreedy
Livelock
• Errore diprogetto che generauna sequenza
ciclica dioperazioni inutili ai fini dell’effettivo
avanzamento della computazione
• Esempio:
– Lasequenza infinita di“vada primalei”
• Diverso daldeadlock:lacomputazione nonè
bloccata,qualcosa viene fatto mamai niente
diutile
Guardedblocks
• Comeevitareilprelievoseilcontovainrosso?
public class ContoCorrente {
private float saldo;
}
public synchronized void prelievo (float soldi){
while (saldo-soldi<0) wait();
saldo -= soldi;
}
Rilasciaillocke
sospendeilthread!
Comerisvegliareuntaskinwait?
public class ContoCorrente {
private float saldo;
public ContoCorrente (float saldoI) {
saldo = saldoI;
}
synchronized public void deposito (float soldi) {
saldo += soldi;
Risvegliauntask(sceltoa
notify();
caso)insospesodawaitsu
}
questooggetto,seesiste
}
public synchronized void prelievo (float soldi) {
while (saldo-soldi < 0) wait();
saldo -= soldi;
Potrebbenonessere
sufficiente...Ilthread
}
potrebberipartireanche
senzaesserenotificatoda
nessun’altrothread...
Esempio:unacodaFIFOcondivisa
• Operazione diinserimento dielemento
– Sospende tasksecodapiena
• while(codaPiena())wait();
– Altermine
• notify();
• Operazione diestrazione dielemento
– Sospende tasksecodavuota
• while(codaVuota())wait();
– Altermine
• notify();
Invece,notifyAllrisveglia
tuttiitaskeventualmente
unwaitsuun
determinatooggetto,ma
unosologuadagnaillock!
public class FIFO {
private boolean empty;
private String message;
public synchronized String take() {
// Wait until message is available.
while (empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = true;
// Notify producer that status has changed.
notifyAll();
return message;
}
}
public synchronized void put(String message) {
// Wait until message has been retrieved.
while (!empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = false;
// Store message.
this.message = message;
// Notify consumer that status has changed.
notifyAll();
}
Ciclodivitadiunthread
born
start
notify
notifyAll
ready
scheduler
wait
running
I/O
lock
waiting
dead
fineI/O
finelock
blocked
Problemiconoggettimutabili
public class SynchronizedRGB {
// Values must be between 0 and 255
private int red;
private int green;
private int blue;
private String name;
private void check(int red, int green,int blue) {//…}
public SynchronizedRGB(int red, int green, int blue, String name) {//…}
public void set(int red, int green, int blue, String name) {
check(red, green, blue);
synchronized (this) {
this.red = red;
this.green = green;
Cambialostato
this.blue = blue;
dell’oggetto
this.name = name;
indipendentemente
}
daglialtrimetodi
}
“getter”..
}
public synchronized int getRGB() {//…}
public synchronized String getName () {//…}
public synchronized void invert () {//…}
Problema
SynchronizedRGB color =
new SynchronizedRGB(0,0,0, “Pitch Black”);
...
int myColorInt = color.getRGB();
//Statement 1
String myColorName = color.getName(); //Statement 2
• Seunaltrothreadinvocaset dopoStatement1
maprimadiStatement2,ilvaloredimyColorInt
noncorrispondealvaloredimyColorName
• Ilproblemasorgeperchél’oggettoèmutabile
Comecreareoggettiimmutabili
• Nonfornire metodi "setter"
• Definire tutti gli attributi diistanza finaleprivate
• Nonconsentire alle sottoclassi difareoverridedei metodi
– Dichiarando laclasse finaloppure dichiarando il costruttore privatee
costruendo gli oggetti mediante factorymethod
• Segli attributi diistanza hanno riferimenti aoggetti mutabili,non
consentire laloro modifica
– Nonfornire metodi che modificano oggetti mutabili
– Nonfaresharingdirefaoggetti mutabili
– Nonsalvare riferimenti aoggetti esterni mutabili passati al
costruttore,senecessario farecopie esalvare riferimenti alle copie
– Inoltre creare copie degli oggetti interni mutabili senecessario per
evitare direstituire gli originali attraverso i metodi
EsempioImmutableRGB
• Cisonoduemetodisetter
– Ilprimo(set)trasformaarbitrariamentel’oggettoenon
avràalcuncorrispettivonellaversioneimmutabile
– Ilsecondo,invert,vieneadattatocreandounnuovo
oggettoinvecedimodificarel’oggettocorrente
• Tuttigliattributisonogiàprivate;vengono
ulteriormentequalificatifinal
• Laclassevienequalificatafinal
• Unsoloattributofariferimentoaunoggetto,e
l’oggettoèimmutabile
– Nonèquindinecessariofarnullapersalvaguardarelo
statodieventualioggettimutabilicontenuti
SoluzioneImmutableRGB
final public class ImmutableRGB {
// Values must be between 0 and 255.
final private int red;
final private int green;
final private int blue;
final private String name;
private void check(int red, int green, int blue) {
if (red < 0 || red > 255
|| green < 0 || green > 255
|| blue < 0 || blue > 255) {
throw new IllegalArgumentException();
}
}
}
public ImmutableRGB(int red, int green, int blue, String name) {
check(red, green, blue);
this.red = red;
this.green = green;
this.blue = blue;
this.name = name;
}
Concettiavanzati
• Finora abbiamo visto i concetti dibase,che
però risultano inadeguati aun’effettiva
programmazione concorrente
• Vediamo ora concetti introdotti in
java.util.concurrent.*
– Oggetti “lock”
– Esecutori
– Collezioni concorrenti
– Variabili atomiche
Oggetti“lock”- Lockobjects
• Ilcodice synchronizeddefinisce uncaso elementare di
lock(implicito),mameccanismi più sofisticati sono
forniti dalpackagejava.util.concurrent.locks
• Unlock,definito dall’interfaccia Lock,può essere
acquisito daunsolothread,comenel caso degli
“implicitlock” associati acodice synchronized
ReentrantLock
• AreentrantmutualexclusionLockwiththe
samebasicbehaviorastheimplicitmonitor
lockaccessedusingsynchronizedmethods
andstatements,butwithextended
capabilities
– lock()acquiresthelock
– tryLock()acquiresthelockonlyifitisnotheldby
anotherthreadatthetimeofinvocation
– unlock()Attemptstoreleasethislock
Classe BowLoop
import java.util.Random;
public class BowLoop implements Runnable {
private Friend bower;
private Friend bowee;
public BowLoop(Friend bower, Friend bowee) {
this.bower = bower;
this.bowee = bowee;
}
}
public void run() {
Random random = new Random();
for (;;) {
try {
Thread.sleep(random.nextInt(10));
} catch (InterruptedException e) {}
bowee.bow(bower);
}
}
Sospendeil
thread
corrente....
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Friend {
private final String name;
private final Lock lock = new ReentrantLock();
public Friend(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public boolean impendingBow(Friend bower) {
Boolean myLock = false;
Boolean yourLock = false;
try {
myLock = lock.tryLock();
yourLock = bower.lock.tryLock();
} finally {
if (! (myLock && yourLock)) {
if (myLock) lock.unlock();
if (yourLock) bower.lock.unlock();
}
}
return myLock && yourLock;
}
Tentadiacquisire
entrambiilock,
tornatruese
possibile,
altrimentifalse
Ricordatevidi
rilasciareilock
chepotresteaver
parzialmente
acquisito
public void bow(Friend bower){
if (impendingBow(bower)){
try {
System.out.format("%s: %s has” + " bowed to me!%n",
this.name, bower.getName());
bower.bowBack(this);
Searriviamoqui
} finally {
lock.unlock();
abbiamoacquisito
bower.lock.unlock();
entrambiilocke
}
dobbiamo
} else {
rilasciarli
System.out.format("%s: %s started” +
" to bow to me, but saw that” +
" I was already bowing to” +
" him.%n”, this.name, bower.getName());
}
}
public void bowBack(Friend bower) {
System.out.format("%s: %s has" + " bowed back to me!%n",
this.name, bower.getName());
}
public class ThreadTest {
public static void main(String[] args) {
final Friend giacomo = new Friend("Giacomo");
final Friend anna = new Friend("Anna");
}
}
new Thread(new BowLoop(giacomo, anna)).start();
new Thread(new BowLoop(anna, giacomo)).start();
Tenterannodiinchinarsiinmomenti
diversi,alcuniriusciranno,altrinoquando
tentanodifarloallostessotempo
Esecutori
• Glistrumentifinoradisponibiliimpongonounastretta
relazionetrailcompitochedeveessereeseguitodaun
thread(definitodall’oggettoRunnable)eilthread
stesso,definitodall’oggettoThread
• Idueconcettipossonoesseretenutidistintiin
applicazionicomplesse,mediante
– interfacceExecutor
– threadpools
– fork/join
• Gliesecutori sonopredefinitieconsentonouna
gestioneefficientecheriduceilpesanteoverhead
dovutoallagestionedeithread
InterfacceExecutors
• Ilpackagejava.util.concurrentdefinisce3interfacce
– Executor
– ExecutorService(estendeExecutor)
– ScheduledExecutorService(estendeExecutorService)
• UtilizzodiExecutor
– SerèunRunnableedeèunExecutor,invecedi
(newThread(r)).start();
facciamo
e.execute(r);
– …evitandol’overheaddovutoallacreazionedeglioggetti
thread
ImplementazionediExecutor
• Leimplementazioni dell’interfaccia Executor
usano threadpools,ovvero thread(innumero
finito)che esistono aldifuori diRunnable
• Unesempio comune è unexecutorche usa un
“fixedthreadpool”,che viene creato chiamando
il “factorymethod”newFixedThreadPool()della
classe java.util.concurrent.Executors
• Gli oggetti Runnablesono inviati alpool
attraverso una coda
– Sei threadesistenti sono tutti occupati,tocca
aspettare…
Collezioniconcorrenti
• Ilpackagejava.util.concurrentinclude
estensioniacollezionidiJava,quali
– BlockingQueue
• StrutturadatiFIFOchebloccaodàtimeoutsesicerca
diinserireincodapienaoestrarredacodavuota
– ConcurrentMap
• Consenteinmanieraatomicadieliminare/modificare
unacoppiachiave-valoresolosechiavepresentee
aggiungerechiave-valoresoloseassente
Variabiliatomiche
• Ilpackagejava.util.atomicdefinisceclassiche
supportanooperazioniatomichesusingole
variabili
• Esseposseggonotuttemetodigetesetchesi
comportanocomeletturaescritturadelle
corrispondentivariabilinonatomiche
• Èdisponibileancheun’operazione
compareAndSet
Oggettiatomici
• Consentonodievitareiproblemidilivenesschepossono
esserecausatidall’usodeimetodisynchronized
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicCounter {
private AtomicInteger c = new AtomicInteger(0);
public void increment() {
c.incrementAndGet();
}
public void decrement() {
c.decrementAndGet();
}
}
public int value() {
return c.get();
}
Scarica