1213 threads [4] sincronització

63
PROGRAMACIÓ CONCURRENT Sincronització 4

Upload: oriol-torres

Post on 01-Jul-2015

105 views

Category:

Documents


5 download

TRANSCRIPT

PROGRAMACIÓ  CONCURRENT  

Sincronització  4  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Els   fils   (threads)   tenen   un   costat  fosc :   Poden donar l loc a problemes de concurrència.    

Els problemes de concurrència   donen   lloc   a   “condicions   de  carrera“  (race  condi*ons).  Les  “condicions  de  carrera”  donen  lloc  a  dades  corruptes.      Hem  arribat  a  una  situació   fa1dica:  Dos  o  més  fils  accedint  a   les  dades  d’un  únic  objecte.      Quan  un  fil  no  està  acMu  o  corrent  és  com  si  esMgués   inconscient.  Quan  (de  nou)  és  acMvat  no  sap  que  ha  estat  aturat.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  Exemple  1:  Molta  llet  

Hora   Persona  1   Persona  2  15:00   Mirar  en  la  nevera:  No  hi  ha  

llet!!  15:05   SorMr  al  carrer  15:10   Arribar  a  la  boMga   Mirar  en  la  nevera:  No  hi  ha  llet!!  15:15   Comprar  un  tetra-­‐brick  de  llet   SorMr  al  carrer  15:20   Posar  el  brick  de  llet  en  la  

nevera  Arribar  a  la  boMga  

15:25   Comprar  un  tetra-­‐brick  de  llet  15:30   Posar  el  brick  de  llet  en  la  nevera.    

Ep!!  Ja  n’hi  ha!!  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  Exemple  1:  Molta  llet  

Variable compartida Tasca a realitzar

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  Exemple  1:  Molta  llet  

Iniciem els fils d’execució amb la tasca a realitzar. Comparteixen el mateix objecte nevera!!

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  

En  Ricard  s’adorm  després  d’haver  comprovat   el   saldo   del   compte  corrent   (100€)   però   abans   de  confirmar   l’execució  d’un     traspàs  de  90€  a  un  altre  compte  corrent.  

Quan   en   Ricard   es   desperta,   immediatament   finalitza   l’execució   del   traspàs   de   diners  

sense  haver  comprovar  (un  altre  cop)  el  saldo  del  compte  corrent.  →  El  saldo  del  compte  corrent  passa  a  estar  en  números  vermells!!  

La   TaMana   -­‐   parella   de’n  Ricard   -­‐   se’n   va   a   un  caixer   per   treure   diners  (90€).     Consulta   el   saldo    per   confirmar   que   tot  està   correcte   (100€).   I  treu  els  diners.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  

Què  ha  passat?    2 fils (Ricard i Tatiana) comparteixen un mateix objecte (el compte corrent).  El  codi  té  dues  classes,  Comptecorrent,  i  TascaTatiRicard.    La   classe   TascaTatiRicard   implementa   un    Runnable i  representa  el  comportament  que  tenen  tant  en  Ricard  com  la  TaMana  –  comprovar  el  saldo  del   compte   i   reMrar   diners   –.   Lògicament,   cada   fil  (thread)  cau  adormit  entre  les  dues  comprovacions  del  saldo  i,  de  fet,  quan  es  fa  la  reMrada  de  diners.    La  classe  TascaTatiRicard   té  una  variable  de  Mpus  CompteCorrent   que   representa   el   seu   propi  compte  corrent  comparMt.  

CompteCorrent

int saldo

mostraSaldo()

retirar() TascaTatiRicard

compteCorrent compte

run()

retirarDiners()

Runnable

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    q  Creem una instància de TascaTatiRicard.

 La  classe  TascaTatiRicard  és  el  Runnable  (la  feina  a  fer)  I,  donat  que  tant  la   TaM   com  en  Ricard   fan   el  mateix   (comprovar   el   saldo   I   reMrar   diners),  necessitem  realitzar  una  única  instància.  

 tascaTatiRicard laTasca = new tascaTatiRicard ();

q  Creem 2 fils amb el mateix Runnable (la instància TascaTatiRicard)

 thread u = new Thread (laTasca); thread dos = new Thread (laTasca);

q  Anomenem i arranquem els fils (threads) u.setName (”Ricard”); dos.setName (”Tatiana”) ; u.start( ); dos.start( );

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    q  Observem com els fils executen el mètode run ()

(comprovar el saldo I retirar diners)

Un  fil   representa  en  Ricard   I   l’altre  representa   la  TaMana.  Els  dos   fils   estan   congnuament   comprovant   el   saldo   i   realitzen  una  reMrada  de  diners  sempre  I  quan  sigui  possible.  

 if (CompteCorrent.mostrarSaldo ( ) ≥ quantitat) {

try { Thread.sleep(500); } catch (InterruptedException ex) {ex.printstackTrace ( ); }

}

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    class  compteCorrent  {  

 private  int  saldo  =  100;  public  int  mostrarSaldo  ()  {  

return  saldo;  }  

public  void  reMrar  (int  quanMtat)  {  saldo  =  saldo  -­‐  quanMtat;  }  

}    public  class  tascaTaMRicard  implements  Runnable  {    private  compteCorrent  compte  =  new  CompteCorrent();    public  staMc  void  main  (String  []  args)  {  

tascaTaMRicard  laTasca  =  new  tascaTaMRicard  ();  Thread  u  =  new  Thread  (laTasca);  Thread  dos  =  new  Thread  (laTasca);  u.setName(“Ricard”);  dos.setName(“TaMana”);  u.start();  dos.start();  

}  

El compte comença amb un saldo de 100€.

Únicament hi haurà UNA instància de tascaTatiRicard. Això vol dir solament UNA instància del compte corrent. Ambdós fils accediran a aquest únic compte corrent.

Instanciem el Runnable (tasca)

Construïm dos fils; donem a cadascun d’ells la mateixa feina del Runnable. Ambdós fils accediran a la variable de instància de compte en la classe Runnable.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    public  void  run  ()  {  

for  (int  x  =  0;  x  <  10;  x++)  {  reMrarDiners  (10)  ;  

if  (compteCorrent.mostraSaldo()  <  0)  {  System.out.println(“Números  vermells");  

}  }  

}    private  void  reMrarDiners  (int  quanMtat)  {  

if  (compte.mostrarSaldo()  >=  quanMtat)  {  System.out.println(Thread.currentThread()  .getName()  +  “  està  a  punt  de  reMrar  diners”);  

try  {  System.out.println  (Thread  .  currentThread()  .getName()  +  “  s’adormirà”);  Thread  .sleep(500);  

}  catch(InterruptedExcepMon  ex)  {ex.printStackTrace();  }  System.out.println  (Thread.  currentThread()  .getName()  +  “  s’ha  despertat”);  compte.reMrar(quanMtat);  System.out.println  (Thread.  currentThread()  .getName()  +  “  Completa  la  reMrada”);  }  else  {  System.out.println(“Ho  senMm,no  hi  ha  fons  per  ”  +  Thread.currentThread().getName());  }  

}  }  

En el mètode run (), un fil entra en un bucle i intenta fer una retirada amb cada iteració. Després de la retirada de diners, comprova el saldo un cop més per comprovar si el compte està en números vermells.

Comprovem el saldo del compte i, si no hi ha prou diners, mostrem un missatge. Si hi ha prou diners, ens anem a dormir; posteriorment, ens despertem i acabem la retirada (tal i com va fer en Ricard).

Imprimim per pantalla un grapat de declaracions per poder veure el que està succeint mentre s'executa.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    El  mètode  reMrarDiners()  sempre  comprova  el  saldo  abans  de  realitzar   la  reMrada   de   diners;   tot   i   això,   el   compte   sempre   es   queda   en   números  vermells.    Un possible escenari: En   Ricard   comprova   el   saldo,   mira     que   hi   ha   prou   diners   i,   llavors,  s’adorm.    Mentrestant,  la  TaMana  va  i  comprova  el  saldo.  Ella,  també,  veu  que  hi  ha  prou   diners.   No   té   ni   idea   que   en   Ricard   es   despertarà   I   completarà   la  reMrada  de  diners.    En  Ricard  es  desperta  i  enllesteix  la  reMrada  de  diners.    La  TaM  “es  desperta”  I  completa  la  seva  reMrada.  Gran  problema!!  Entre  el  moment   en   que   ella   ha   comprovat   el   saldo   i   que   ha   fet   la   reMrada,   en  Ricard  s’ha  despertat  i  ha  reMrat  diners  del  compte..      La  comprovació  del  compte  per  part  de  la  TaMana  no  és  vàlida  perquè  en  Ricard  ja  havia  comprovat  I  estava  enmig  de  la  realització  de  la  reMrada.    L’accés  al  compte  per  part  de  la  TaMana  s’hagués  hagut  d’aturar  fins  que  en   Ricard   hagués   despertat   I   hagués   finalitzat   la   seva   transacció.   I   a  l’inrevés,  també.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

Necessiten un cadenat per gestionar l’accés al

compte

Hi   hauria   un   cadenat   associat   amb   les  transaccions   dels   comptes   bancaris  (comprovació  del  saldo  i  reMrada  de  diners).  Solament   hi   hauria   una   clau   que   es  manMndria   amb   el   cadenat   fins   que   algú  volgués  accedir  al  compte.  

 1

L a t r a n s a c c i ó d e l compte bancari està oberta quan ningú està utilitzant el compte.

El  cadenat  treballaria  tal  I  com  segueix:  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

Quan   en   Ricard   volgués   accedir   al  compte  corrent  del  banc  (per  comprovar  el   saldo   i   realitzar   una   reMrada   de  diners),  tancaria  el  cadenat  I  es  guardaria  la  cau  a  la  butxaca.      En   conseqüència,   ningú   més   podria  accedir   al   compte   donat   que   la   clau   ja  no  estaria  disponible.  

 2

Quan en Ricard vol accedir al compte, tanca el cadenat i es queda amb la clau.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

En  Ricard  manKndria  la  clau  a  la  butxaca  fins  que   finalitzés   la   transacció.   Com   que   ell  Mndria   l’única   clau   pel   cadenat,   la   TaMana   no  podria  accedir  al  compte  (o  a  la  comprovació)  fins  que  en  Ricard  hagués  deixat  el  cadenat,  de  nou,  obert.    Inclús  si  en  Ricard  caigués  adormit  després  de  comprovar  el  saldo,  Kndria  la  garanKa  de  que  el   saldo   seria   el   mateix   quan   es   despertés  perquè  mentre   hagués   dormit,   hauria   Kngut  la  clau  guardada  en  la  seva  butxaca.  

 3

Quan en Ricard acaba, o b r e e l c a d e n at i retorna la clau. Ara la clau està disponible per la Tati o per en Ricard per si, de nou, vol accedir al compte.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

Necessitem  que  el  mètode  reMrarDiners  ()  funcioni  com  quelcom  atòmic.  

Necessitem  assegurar-­‐nos  de  que,  un  cop  que  el  fil  entri  en  el  mètode  reMrarDiners   (),  pugui  finalitzar  el  mètode  abans  de  que  qualsevol  altre  fil  pugui  entrar-­‐hi.    És  a  dir,  que  un  cop  que  un  fil  hagi  comprovat  el  saldo,  aquest   Mndrà   la   garanMa   de   que   pot   adormir-­‐se   i  despertar  més  tard  i  finalitzar  la  reMrada  de  diners  abans  de  que  qualsevol  altre  fil  pugui  comprovar  el  saldo.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

Necessitem  que  el  mètode  reMrarDiners  ()  funcioni  com  quelcom  atòmic.  

En programació, una acció ATÒMICA és aquella que succeix amb efectivitat en/d’un sol vez. Una acció atòmica no es pot aturar a mig fer (o succeeix completament o no succeeix en absolut).

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

Si   uMlitzem   la   paraula   clau   synchronized  podrem   modificar   un   mètode   perquè   tant  sols  un  fil  pugui  accedir-­‐hi.    En   el   cas   de   l’exemple,   serà   així   com  protegirem  el  compte  corrent  del  banc.    No  posarem  un  cadenat  al  compte  del  banc.      Posarem  el  cadenat  al    que  realitza  la   transacció   bancària.   Així,   un   fil   podrà  completar  tota  una  transacció  –  de  principi  a  fi   –   inclús   si   el   fil   s'adormís   a   meitat   del  mètode.  

La paraula clau synchronized vol dir que un fil necessita una clau per accedir al codi sincronitzat. Per protegir les nostres d a d e s , h a u r e m d e sincronitzar els mètodes que actuïn sobre aquestes dades.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  2:  Two  people,  one  account  –  Codi  resultant    

private    void  reMrarDiners  (int  quanMtat)  {    If  (compte.mostrarSaldo()  >=  quanMtat)  {  

System.out.println(Thread.currentThread().getName()  +"  està  a  punt  de  reMrar  diners");  try  {  

System.out.println  (Thread.currentThread().getName()  +"  s'adormirà");  Thread.sleep(500);  

}  catch(InterruptedExcepMon  ex)  {ex.printStackTrace();  }  System.out.println  (Thread.currentThread().getName()  +  "  ja  s'ha  despertat");  compte.reMrar(quanMtat);  System.out.println  (Thread.currentThread().getName()  +  "  completa  la  reMrada");  }  

else  {  System.out.println("Ho  senMm,no  hi  ha  fons  per  "  +  Thread.currentThread().getName());  

}  }  

}  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  1:  Molta  llet  -­‐  Solució  

La  sincronització  permet  definir  ordres  estrictes  d’accés  i  execució.      

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Cadenats  i  sincronització  

•  Cada  objecte  de  Java  té  un  cadenat.  Un  cadenat  té  una  única  clau.  La  majoria  del  temps,  estan  oberts.  

•  Els   cadenats   dels   objectes   entren   en   joc   quan   hi   ha  mètodes   sincronitzats.   Aquests   cadenats   és   el   que  coneixerem  com  a   .  

•  La   finalitat   de   la   sincronització   és   protegir   les   dades  críMques.   No   protegirem   sota   cadenat   les   dades,  sincronitzarem  els  mètodes  que  accedeixin  a  aquestes  dades.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda  

Hi  ha  un  altre  problema  clàssic  de  concurrència  que  prové  del  món  de  les  bases  de  dades.  

L’actualització  perduda  gira  al  voltant  d’un  procés:  Pas 1: Agafa el saldo del compte

int i = saldo; Pas 2: Suma 1 a aquest saldo

saldo = i + 1; El truc per mostrar això és forçar l'equip a fer 2 passos per completar el canvi en l'equilibri. En el món real, faríem aquest moviment particular en una sola sentència: saldo++; Ara bé, forçant-ho a 2 passos, poden aparèixer els problemes propis d’un procés no atòmic.

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda  

Imaginem   que,   enlloc   dels   passos   trivials  “obMngues   el   saldo   i   suma-­‐hi   1”,   els   dos   (o  més  passos)  d’aquest  mètode  són  molt  més  complexos  i,  per  tant,  no  es  pot  fer  amb  una  única  sentència.  

En  el  problema  de  l’actualització  perduda,  tenim  2  fils,  ambdós  intentant  incrementar  el  saldo.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda  class  TestSync  implements  Runnable  {    

 private  int  saldo;    

 public  void  run  ()  {      for  (int  i=0;  i<50;  i++)  {      increment  ();      System.out.println(“El  saldo  és  “  +  saldo);      }    }  

   public  void  increment  ()  {      int  I  =  saldo;      saldo  =  I  +  1;  

     }    }  

   public  class  TestSyncTest  {      public  staMc  void  main  (String[]  args)  {        TestSync  tasca  =  new  TestSync  ();        Thread  a  =  new  Thread  (tasca);        Thread  b  =  new  Thread  (tasca);        a.start  ();        b.start  ();        }      }  

Cada  fil  corre  50  cops,  incrementant  el  saldo  

amb  cada  iteració.  

Aquí  hi  ha  el  punt  crucial!!  Incrementem  el  saldo  afegint  1  a  sigui  quin  sigui  el  

valor  del  saldo    en  el  moment  de  llegir-­‐ho  (enlloc  d’afegir  1  a  sigui  quin  sigui  el  

valor  actual).  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda  

El  fil  A  funciona  durant  un  temps:  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  és  0,  per  tant,  i  ara  és  0.  •  Actualitzem  saldo  amb  el  resultat  de  i+1.  •  Ara,  saldo  és  1.  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  val  1,  per  tant,  i  ara  és  1.  •  Actualitzem  saldo  amb  el  resultant  de  i+1.  •  Ara  saldo  val  2.  

El  fil  B  funciona  durant  un  temps:  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  és  2,  per  tant,  i  ara  és  2.  •  Actualitzem  saldo  amb  el  resultat  de  i+1.  •  Ara,  saldo  és  3.  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  val  3,  per  tant,  i  ara  és  3.  [Ara  el  fil  B  és  enviat  de  tornada  cap  a  runnable;  abans  de  que  posi  el  valor  de  saldo  a  4].  

El  fil  A  arrenca  de  nou  a  parMr  del  punt  on  ho  havia  deixat:  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  és  3,  per  tant,  i  ara  és  3.  •  Actualitzem  saldo  amb  el  resultat  de  i+1.  •  Ara,  saldo  és  4.  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  val  4,  per  tant,  i  ara  és  4.  •  Actualitzem  saldo  amb  el  resultant  de  i+1.  •  Ara  saldo  val  5.  

El  fil  B  arrenca  de  nou  a  parMr  del  punt  on  ho  havia  deixat:  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  Ara,  saldo  és  4.  

Ep!   El   fil   A   el   va   actualitzar   a   5,  però   ara   ha   tornat   el   B   al  començament   de   l’actualització  de   l’A   com   si   aquest   no   hagués  fet  res!!  

 1  2

 3  4

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda:  Solució  

Sincronitzant  el  mètode  increment  ()  solucionem  el  problema  de  l’actualització  perduda”  perquè  manté  els  dos  passos  en  el  mètode  com  una  única  i  sòlida  unitat.    public    void  increment  ()  {  

   int  i  =  saldo;      saldo  =  I  +  1;  

     }    }  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  4:  Interferència  entre  fils  

class Counter { private int c = 0; public void increment() { c++; } public void decrement() { c--; } public int valor() { return c; } } c++  està  compost  de:  1)  Obtenir  el  valor  de  c  2)  Incrementar  c  en  1  3)  Emmagatzemar  el  valor  de  c    c-­‐-­‐  està  compost  de:  4)  Obtenir  el  valor  de  5)  Decrementar  c  en  1  6)  Emmagatzemar  el  valor  de  c

Dos fils A i B poden espatllar-ho:

Fil A: Recuperar c (0) Fil B: Recuperar c (0) Fil A: Increment c (1) Fil B: Decrement c (-1) Fil A: Emmagatzemar c (1) Fil B: emmagatzemar c (-1)

   

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  4:  Interferència  entre  fils  

class Counter { private int c = 0; public synchronized void increment() { c++; } public synchronized void decrement() { c--; } public int valor() { return c; } }

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  3:  El  temut  problema  de  l’actualització  perduda  El  fil  A  funciona  durant  un  temps:  •  Prova  d’accedir  al  mètode  increment  ().  •  El  mètode  està  sincronitzat;  agafa  la  clau  

per  aquest  objecte.  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  La  variable  saldo  és  0  i,  ara  i  és  0.  •  Posem  a  saldo  el  resultat  de  i  +  1.  •  Ara  saldo  val  1.  •  Retornem  la  clau  (això  completa  el  mètode  

increment  ().  •  Tornem  a  entrar  en  el  mètode  increment  ()  i  

agafem  la  clau.  •  Posem  el  valor  de  saldo  en  la  variable  i.  •  Ara  saldo  val  1;  per  tant,  ara  i  val  1.  [Ara  el  fil  A  és  retornat  a  runnable,  però  com  que  no  ha  completat  el  mètode  sincronitzat,  manté  la  clau]  

El  fil  B  és  seleccionat  per  funcionar:  •  Prova  d’accedir  al  mètode  increment  ().  

Com  que  el  mètode  està  sincronitzat,  necessitem  agafar  la  clau.  

•  La  clau  no  es  troba  disponible.  [Ara  el  fil  B  és  enviat  a  una  “sala  de  cadenats  d’objecte  no  disponible”].  

 1  2

 3

 4

El  fil  B  arrenca  de  nou  a  parMr  del  punt  on  ho  havia  deixat  (encara  té  la  clau):  •  Posa  a  saldo  el  resultat  de  i+1.  •  Ara,  saldo  és  2.  •  Retorna  la  clau.  [Ara  el  fil  A  és  retornat  a  runnable  però,  com  que  ja  ha  completat  el  mètode  increment  (),  el  fil  no  manté  la  clau].  

El  fil  B  és  seleccionat  per  funcionar:  •  Prova  d’accedir  al  mètode  increment  ().  El  mètode  està  sincronitzat.  

Necessitem  la  clau.  •  Aquest  cop,  la  clau  està  disponible.  L’agafem.  •  Posem  el  valor  de  saldo  en  la  variable  i.  [ConMnua  funcionant..  ]  

És  una  bona  idea  sincronitzar-­‐ho  tot?  Per  si  de  cas..  

•  No;  els  mètodes  sincronitzats  comporten  despeses  generals.  Quan  el  codi  arriba  a  un  mètode  sincronitzat,  hi  ha  un  impacte  en  el  rendiment  –  normalment,  no  es  percep  –  mentre    es  confirma  si  “la  clau  del  cadenat”    està  disponible.  

•  Un  mètode  pot  alenMr  el  programa  per  les  restriccions  que  defineix.  Un  mètode  sincronitzat  força  a  la  resta  de  fils  a  estar  disponibles  i  esperar  el  seu  torn.  

•  Els  mètodes  sincronitzats  poden  donar  lloc  a  un  punt  mort.  

Una  regla  d’or  a  seguir  és  sincronitzar  només  el  mínim  del  que  

ha  d'estar  sincronitzat.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

Una  regla  d’or  a  seguir  és  sincronitzar  només  el  mínim  del  que  ha  d'estar  sincronitzat.  

public  void  go  ()  {    fesFeina  ();  

 synchronized(this)  {  

 feinaCriMca  ();    mesFeinaCriMca  ();    }  

}  

fesFeina  ()  no  necessita  ser  sincronitzat;  per  tant,  no  sincronitzarem  tot  el  mètode  sencer.  

Ara,  només  aquestes  dues  crides  als  mètodes  s’agrupen  en  una  unitat  atòmica.  Quan  s'uMlitza  la  paraula  clau  synchronized  dins  d'un  mètode,  enlloc  d'una  declaració  de  mètode,  hem  de  proporcionar  un  argument  que  és  l'objecte  la  clau  del  qual  el  fil  ha  d’aconseguir.   Tot  i  que  hi  ha  altres  maneres  per  fer-­‐ho,  

quasi  sempre  sincronitzarem  l’objecte  actual  (this).  De  fet,  és  el  mateix  objecte  que  tancariem  (lock)  si  sincronitzessim  el  mètode  sencer.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Exemple  1:  Molta  llet  –  Solució  2  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 Codi  sincronitzat  

•  Enlloc   de   sincronitzar   tot   un   mètode   podem  sincronitzar  una  porció  de  codi.  

 

public void addName(String name) {

synchronized(this); {

lastName=name; nameCount++;

} nameList.add(name);

}

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  Java   uMlitza  monitors   per   la   sincronització   de  fils.  

•  Tot  objecte  amb  mètodes    és  un  monitor.  

•  El  monitor  solament  permet  a  un  fil  per  ocasió  executar   un   mètode     sobre  l’objecte.  

•  Los   mètodes   que   hagin   d’accedir   al   mateix  objecte,   s’han   de   declarar     per  aconseguir  el  bloqueig.  

•  L’execució   de   dos   mètodes   sincronitzats   és  mútuament  excloent.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

Fil esperant

Fil actiu

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 //Aquest programa no està sincronitzat. class Crida {

void crit (String msg){ System.out.print("["+msg); try { Thread.sleep(1000); }catch(InterruptedException e) {;} System.out.print("]"); }

}

public class ElQueCrida extends Thread { String msg; Crida objectiu; Thread t; public ElQueCrida(Crida objectiu, String msg) { this.objectiu = objectiu; this.msg = msg; t = new Thread(this); t.start(); } public void run() { objectiu.crit(msg); }

}

class Sincro { public static void main(String[] args){ Crida objectiu = new Crida();

ElQueCrida ob1 = new ElQueCrida(objectiu, "Hola"); ElQueCrida ob2 = new ElQueCrida(objectiu, "Mon"); ElQueCrida ob3 = new

ElQueCrida(objectiu, "Sincronitzat");

//Esperem a que els fils acabin try { ob1.t.join(); ob2.t.join(); ob3.t.join(); }catch(InterruptedException e){;}

} }

On  haig  d’aplicar  el  monitor?  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

Sincronització:  Monitors  –  Exemple  //Aquest programa no està sincronitzat. class Crida {

void crit (String msg){ System.out.print("["+msg); try { Thread.sleep(1000); }catch(InterruptedException e) {;} System.out.print("]"); }

}

public class ElQueCrida extends Thread { String msg; Crida objectiu; Thread t; public ElQueCrida(Crida objectiu, String msg) { this.objectiu = objectiu; this.msg = msg; t = new Thread(this); t.start(); } public void run() { objectiu.crit(msg); }

}

class Sincro { public static void main(String[] args){ Crida objectiu = new Crida();

ElQueCrida ob1 = new ElQueCrida(objectiu, "Hola"); ElQueCrida ob2 = new ElQueCrida(objectiu, "Mon"); ElQueCrida ob3 = new

ElQueCrida(objectiu, "Sincronitzat");

//Esperem a que els fils acabin try { ob1.t.join(); ob2.t.join(); ob3.t.join(); }catch(InterruptedException e){;}

} }

On  haig  d’aplicar  el  monitor?   class Crida {

synchronized void crit(String msg){ //…

}

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

§  (Fins ara..)  Casos/Exemples  amb  fils  asíncrons  i  independents  §  Cada  fil  conté   totes   les  dades   i  els  mètodes  necessaris   i  no  

requereix  recursos  externs.  §  Els  fils  s’executen  en  el  seu  propi  espai;  no  els  influeix  l’estat  

o  l’acMvitat  d’altres  fils  que  s’execuMn  de  forma  concurrent.    §  (Noves situacions)  Fins  concurrents  que  comparteixen  dades   i   han   de   considerar   l’estat   i   l’acMvitat   d’altres  fils.  §  Escenaris  “productor/consumidor”.  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

§  Escenari  “productor  –  consumidor”  §  El   PRODUCTOR   genera   un   canal   de   dades   que   és  consumit  pel  CONSUMIDOR.  

Productor   Consumidor  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  Podem   imaginar  una  aplicació   JAVA  on  un  fil   (PRODUCTOR)  escriu   dades   en   un   arxiu   mentre   que   un   segon   fil  (CONSUMIDOR)  llegeix  les  dades  del  mateix    arxiu.  

•  si  teclegem  caràcters  en  el  teclat,  el  fil  PRODUCTOR  situa  les  polsacions  en  una  pila  d’esdeveniments  i  el  fil  CONSUMIDOR  llegeix  els  esdeveniments  de  la  mateixa  pila.  

•  Aquests   dos   exemples   uMlitzen   fils   concurrents   que  comparteixen   un   recurs   comú;   el   primer   comparteix   un  arxiu  i  el  segon  una  pila  d’esdeveniments.  

•  Com   que   els   fils   comparteixen   un   recurs   comú,   s’han   de  sincronitzar.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

§  El   PRODUCTOR genera   un   enter   entre   0   i   9,  l’emmagatzema   en   un   objecte     i  imprimeix  el  número  generat.  

§  A   més,   el   PRODUCTOR   dorm   durant   un   temps  aleatori   entre   0   i   100   milisegons   abans   de  repeMr  el  cicle  de  generació  de  números.  

EXEMPLE  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

§  Com   que   el   CONSUMIDOR està   famèlic,  consumeix   tots   els   enters   de   l’objecte  

  tant   ràpid   com   sigui   possible   –  quan   el   número   generat   està   disponible,  l’agafa  –.  

EXEMPLE  

Productor   Consumidor  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

§  El   PRODUCTOR i   el CONSUMIDOR comparteixen  dades   per   mitjà   d’un   objecte   en   comú  ( ).

§  Cap  dels  dos  fa  cap  esforç  per  assegurar-­‐se  de  que  el  CONSUMIDOR  obMngui  cada  valor  produït  un  únic  cop.  

§  La  sincronització  entre  aquests  dos  fils  es  dóna  en  un  nivell   inferior,  dins  dels  mètodes  get()   i  put()  de  l’objecte   .    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

I  si  els  fils  no  esMguessin  sincronitzats?  

 .  .  .  Consumidor  #1  obté:  3  Productor  #1  posa:  4  Productor  #1  posa:  5  Consumidor  #1  obté:  5    .  .  .  

•  Un   problema   seria   quan   el   PRODUCTOR   fos   més   ràpid   que   el  CONSUMIDOR  i  generés  dos  números  abans  de  que  el  CONSUMIDOR  Mngués  una  possibilitat  de  consumir  el  primer  número.  Així,  el  CONSUMIDOR  es  saltaria  un  número.  

•  Part  de  la  sorMda  seria..:  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

I  si  els  fils  no  esMguessin  sincronitzats?  

 .  .  .  Productor  #1  posa:  4  Consumidor  #1  obté:  4  Consumidor  #1  obté:  4  Productor  #1  posa:  5    .  .  .  

•  Un  altre  problema  que  podria  aparèixer  seria  si  el  CONSUMIDOR  fos  més  ràpid  que  el  PRODUCTOR  i  consumís  el  mateix  valor  dos  o  més  cops.    

•  En  conseqüència,  el  CONSUMIDOR  imprimiria  el  mateix  valor  dos  cops.  

•  Part  de  la  sorMda  seria..:  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  Per   prevenir   els   problemes   que   sorgeixen   en  l ’ e x e m p l e   P R O D U C T O R / C O N S U M I D O R ,  l’emmagatzematge  d’un  nou  enter  a  CubbyHole  per  part   del   PRODUCTOR   ha   d’estar   sincronitzat   amb   la  recuperació  per  part  de    CONSUMIDOR.  

•  El   programa   PRODUCTOR/CONSUMIDOR   uMlitza   dos  mecanismes   diferents   per   sincronitzar   els   fils:   els  monitors  i  els  mètodes    noKfy()  i  wait().    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  El   programa   PRODUCTOR/CONSUMIDOR   uMlitza   dos   mecanismes  diferents   per   sincronitzar   els   fils:   els   monitors   i   els   mètodes    noKfy()  i  wait().    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  A  Java  s’associa  un  únic  monitor  per  cada   objecte   que   té   un   mètode  sincronitzat.  

•  La  classe  té  2  mètodes  sincronitzats:  ü  El   mètode   put()   s’uMlitza   per  

canviar  el  valor  de  CubbyHole.  ü  El   mètode   get()   s’uMlitza   per  

recuperar  el  valor  actual.  

•  És   així   com   el   sistema   associa   un  únic  monitor  amb  cada  exemplar  de  CubbyHole.    

Sincronització  dels  fils  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

El  mateix  fil  pot  cridar  a  una  mètode  sincronitzat  d’un  objecte  pel  que  ja  té  el  monitor,  és  a  dir,  pot  tornar  a  adquirir  el  monitor.

•  Sempre   que   el   PRODUCTOR   crida   al  mètode   put()   de   CubbyHole ,  adquireix   el   monitor   de   l’objecte  CubbyHole,   i   així   evita   que   el  consumidor   pugui   cridar   al   mètode  get()  de  CubbyHole.  

•  E l   m è t o d e   wa i t ( )   a l l i b e r a  temporalment  el  monitor.  

•  Quan   el   mètode   put()   retorna,   el  PRODUCTOR   allibera   el   monitor   i  desbloqueja  l’objecte  CubbyHole.    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

El  mateix  fil  pot  cridar  a  una  mètode  sincronitzat  d’un  objecte  pel  que  ja  té  el  monitor,  és  a  dir,  pot  tornar  a  adquirir  el  monitor.

•  Sempre   que   el   CONSUMIDOR    crida   al   mètode   get()   de  CubbyHole ,   adqui re ix   e l  monitor  d’aquest  objecte  i,  per  tant,   evita   que   el   PRODUCTOR  pugui  cridar  al  mètode  put().    

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 Els  mètodes    i    poden  ser  invocats  únicament  des  de  dins  d’un  mètode  

sincronitzat  o  dins  d’un  bloc  o  una  sentència  sincronitzada.  

•  Mètode    El  mètode     fa   que   el   fil   actual   esperi   (probablement,   per   sempre)  fins  que  un  altre  fil  li  ho  noMfiqui  o  que  canviï  una  condició.    El   mètode   s’uMlitza   en   conjunció   amb   el   mètode     per  coordinar  l’acMvitat  de  varis  fils  que  uMlitzen  els  mateixos  recursos.    Quan   el   fil   entra   en   el   mètode   ,   el   monitor   és   alliberat  automàMcament,  i  quan  el  fil  surt  del  mètode   ,  s’adquereix  de  nou  el  monitor.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 Els  mètodes    i    poden  ser  invocats  únicament  des  de  dins  d’un  mètode  

sincronitzat  o  dins  d’un  bloc  o  una  sentència  sincronitzada.  

•  Mètode    El  mètode    escull  un  fil  que  està  esperant  el  monitor  posseït  pel  fil  actual  i  el  desperta.  Normalment,  el  fil  que  es  troba  a  l’espera,  capturarà  el  monitor  i  procedirà.  

•  Mètode    El   mètode     permet   desbloquejar   tots   els   fils   que   s’hagin  bloquejat  amb  la  invocació  del  mètode   .  Quan  envia  la  noMficació  a  tots   els   fils,   aquests   comencen   a   compeMr   per   tancar   el   candau   i,   en  conseqüència,  per  executar-­‐se.    

Normalment,   optem   per   uKlitzar     en   detriment   de     donat   que   no  sabem  es  pot  donar  una  situació  de  coexistència  de  varis  fils  en  el  futur.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  El  fil  CONSUMIDOR  crida  al  mètode  get( ) ,   pe l   que   e l   mètode  CONSUMIDOR   posseeix   el   monitor  de   CubbyHole   durant   l’execució  del  mètode  get().  

•  Al  final  del  mètode  get(),   la  crida  al  mètode  noKfy()   desperta   al   fil  PRODUCTOR   que   obté   el   monitor  de  CubbyHole  i  procedeix.  

•  Si   existeixen   varis   fils   esperant   per   un   monitor,   el   sistema  d’execució   Java   escull   un   d’aquests   fils   sense   cap   compromís   ni  garanMa  sobre  el  fil  que  serà  escollit.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

•  El   bucle   while   conté   la   crida   a  wait().   Aquest   mètode   espera  indefinidament   fins   que   arribi  una  noMficació  del  fil  PRODUCTOR.  

•  Quan   el   mètode   put()   crida   a  noKfy(),   el   CONSUMIDOR   desperta  de   l’estat   d’espera   i   conMnúa  amb  el  bucle.  

•  Presumiblement,   el   PRODUCTOR   ja  ha   generat   un   nou   número   i   el  métode   get()   cau   al   final   del  bucle  i  procedeix.  

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA  

 

PROCESSOS  CONCURRENTS  I  MEMÒRIA  COMPARTIDA