SlideShare una empresa de Scribd logo
1 de 42
Clustering di applicazioni Java SE  con Terracotta Guido Anselmi [email_address] Java User Group Milano http://www.jugmilano.it
Sommario ,[object Object]
Ambiti di utilizzo
Le basi del clustering in Terracotta
Object Roots e Clustered Objects
Virtual Heap
Thread coordination e Locks
Esempi di sincronizzazione/coordinamento tra JVM diverse
Casi d'uso e definizioni Terracotta is a transparent clustering service for Java applications I tipici casi d'uso di Terractotta possono rientrare nelle seguenti macro categorie: •  Distributed cache •  Database offload •  Session replication •  Workload partitioning
Overview Terracotta è un  servizio  di  clustering   trasparente . “ Trasparente ” è riferito al linguaggio Java, in cui il clustering è ottenuto senza alcun modello di programmazione o API specifiche. “ Clustering ” indica la capacità di consentire a macchine e processi Java multipli di lavorare insieme sugli stessi dati e di poter comunicare tra di loro utilizzando semplicamente oggetti in memoria e threading logic.  Terracotta consente alle applicazioni Java di girare su quante macchine necessario, senza alcuna modifica specifica. Non viene  alcun modello specifico di programmazione come clustered caching, EJB, o anche Hibernate o Spring.  Terracotta può prendere un'applicazione esistente e scalarla trasparentemente su molte macchine.
POJO Clustering-Instrumented classes Affinchè Terracotta possa effettivamente operare sul nostro codice ( bytecode  in realtà), occorre indicare nella configurazione quali classi devono essere “ispezionate e controllate”. Questo avviene tramite il file di configurazione, nella seguente sezione: < instrumented-classes > < include > < class-expression > it.jugmilano.terracotta.example1 ..* </ class-expression > </ include > </ instrumented-classes >   Quali classi devono essere instrumented?  Per ragioni di performance è bene limitare l'insieme a quello delle classi che usano root objects.
POJO Clustering - Roots Un oggetto definito come root è l'elemento radice di un grafo di oggetti clusterizzati.  Qualunque variabile di una classe può esere dichiarata come root. La prima volta che nel codice java viene assegnato un valore ad un oggetto root, Terracotta trasforma lo stesso in clustered object, allocandolo sullo heap condiviso. In questo processo, Terracotta naviga l'intero grafo degli oggetti raggiungibili dalla root, ed essi vengono a loro volta allocati sullo heap condiviso. Le roots sono dichiarate nel file di configurazione di Terracotta  <roots> ... <root> <field-name> my.package.MyClass.myRootField </field-name> </root> </roots>
POJO Clustering - Roots I fields dichiarati come root assumono una speciale semantica 1) La prima volta che un field root viene assegnato da una JVM, la root è creata nel cluster Terracotta. 2) Una volta assegnato, il valore di un field root  non può  essere cambiato. Tutti i successivi assegnamenti, di qualunque JVM,  vengono ignorati . 3) L'oggetto top level di un grafo root, non viene mai reclamato dal garbage collector distribuito di Terracotta. Il server Terracotta ha un garbage collector distribuito che rimuove i clustered objects non più referenziati, ossia non più raggiungibili da nessuna root (non facenti parte del grafo di nessuna root) Le roots hanno lo stesso ciclo di vita del clustered heap, e non sono legate alla singola JVM.
POJO Clustering - Roots Ma come fa terracotta a trasformare in maniera trasparente una variabile d'istanza o statica in un oggetto condiviso? Terracotta opera esclusivamente a livello di bytecode. Guidato da quanto indicato nella sezione instrumented-classes, opera una ricerca sulla presenza di oggetti root, e ne trasforma il bytecode. Terracotta si inserisce tra la logica applicativa e la memoria della JVM, ispezionando tutte le chiamate allo heap che coinvolgono roots objects. In particolare, tutte le letture/scritture sullo heap che coinvolgono root objects vengono sostituite da letture/scritture sul clustered heap. HEAPREAD() -> CLUSTEREDHEAPREAD() HEAPWRITE() -> CLUSTEREDHEAPWRITE()
POJO Clustering - Locks Quando diversi threads accedono concorrentemente gli stessi dati sullo heap, speciali precauzioni devono essere prese per evitare interferenze distruttive tra di essi. Naturalmente la cosa è ancora valida quando  diversi threads su diverse jvm  accedono contemporaneamente lo stesso oggetto sul clustered heaps. Su singola JVM, java sin dagli albori offre meccanismi integrati di mutua esclusione e collaborazione, nella forma del costrutto  synchronized  e dei metodi di Object  wait  e  notify.  Nelle ultime versioni del linguaggio è stato inoltre integrato il package  java.util.concurrent , rendendo java un linguaggio con un supporto di primissimo livello al multithreading.
POJO Clustering - Locks Cosa deve cambiare  nel codice quando mutua escliusione e collaborazione assumono una valenza a livello di cluster?  Assolutamente nulla! Terracotta fornisce esattamente le stesse garanzie di serializzazione degli accessi, coordinamento e visibilità a thread in differenti JVM rispetto a quanto fa la singola JVM  sui suoi threads/heap. I meccanismi che Terracotta utilizza per ottemperare a queste semantiche sono i locks. Terracotta amplia la semantica dei lock built in di Java in modo da dargli un effetto a livello di cluster. Il lock clusterizzato è iniettato nel bytecode in base a quanto specificato nella sezione lock
POJO Clustering - Locks <locks> <autolock> <lock-level>write</lock-level> <method-expression>void HelloWorld.method(..)</method-expression> </autolock> </locks> Questa configurazione istruisce terracotta di ispezionare tutti i metodi che matchano la method-expression dentro le instrumented-classes, verificare se al loro interno ci sono accessi synchronized a fields root e, nel caso, sostituire il lock jvm con uno clustered. In luogo di <autolock> è possibile specificare dei <named-locks> La differenza tra i due è che il secondo agisce sempre a livello di intero metodo, mentre il primo onora i blocchi synchronized presenti dentro i metodi.
POJO Clustering - Locks A differenza di Java, i lock di Terracotta hanno diversi livelli: •   write •  synchronous-write •  read  locks Write locks:  sono i classici lock di mutua esclusione: essi garantiscono che solo un thread nell'intero cluster possa ottenere l'accesso all'oggetto protetto. Synchronous write locks  aggiungono la garanzia che il lock non sia rilasciato fin quando le modifiche effettuate dal thread corrente non siano effettivamente propagate al cluster. Read locks:  consentono a diversi thread di acquisire il lock sull'oggetto, a patto di non effettuare nessun operazione di scrittura (pena eccezione runtime) Nessun thread può acquisire un lock a livello write se altri thread detengono un lock a livello read.  Inoltre a nessun thread è consentito di ottenere un lock read se un altro thread detiene un lock a livello write.
Esempio1 Immaginiamo di avere un insieme di threads il cui compito consiste unicamente nell'estrarre dei Job da un coda condivisa, elaborarli e cambiarne lo stato una volta terminato. Tali threads non devono necessariamente essere eseguiti sulla stessa JVM Job1 Job1 Job1 Job1 Job1 Job1 Jobn Job4 Job3 Job2 JVM1 thread1-3 thread1-2 thread1-1 JVM2 thread2-3 thread2-2 thread2-1 JVM3 thread3-3 thread3-2 thread3-1
Esempio1 La coda è evidentemente un oggetto condiviso, quindi va protetta dall'accesso concorrente da parte di thread differenti (che siano sulla stessa o su diverse JVM) Una prima, molto semplificata, soluzione è la seguente: public class SimpleJobQueue implements JobQueue { private List<Job> jobs = new ArrayList<Job>(); public SimpleJobQueue(int size){ for (int i=0;i<size;i++){ jobs.add(new SimpleJob()); } } public Job getJob(Job job) { return null; } public Job getJobAt(int index) { return jobs.get(index); } }
Esempio1 Dove Job è un'interfaccia che potremmo implementare nel modo seguente public interface Job { public static final int  NOT_ASSIGNED   = 0; public static final int  ASSIGNED   = 1; public static final int  COMPLETED   = 2; public void changeStatus(int status); public int getStatus(); } public class SimpleJob implements Job { private int jobStatus; public SimpleJob(){ jobStatus =  NOT_ASSIGNED ; } public void changeStatus(int status) { this.jobStatus = status; } public int getStatus() { return jobStatus; } }
Esempio1 Ed infine il terzo attore, il thread che fa il lavoro sporco, prelevandolo dalla coda. public class SimpleWorker implements Runnable { .................................... public void run() { int status; Job job; synchronized ( queue ) { job = queue.getJobAt( index ); status = job.getStatus(); log(&quot;Extracted Job &quot; +  index  + &quot; in status &quot; + status);   if (status == Job. NOT_ASSIGNED ) { job.changeStatus(Job. ASSIGNED ); log(&quot;Assigned Job &quot; +  index ); try {Thread. sleep (1000);} catch(InterruptedException e){ } } job.changeStatus(Job. COMPLETED ); log(&quot;Completed Job &quot; +  index ); }  else  {  log(&quot;Job &quot; +  index  + &quot; Allready Assigned - Nothing TO DO&quot;);  } }  }
Esempio1 Vogliamo adesso testare il nostro piccolo sistema, costruendo una coda contenente 10 jobs e lanciando 30 diversi threads che accedono la coda stessa, in tre diverse JVM. Senza troppa sorpresa, occorre scrivere una classe con un metodo main e lanciarlo tre volte, ad esempio passandolo all'interprete java in tre diverse shell. Proviamo a scriverlo.
Esempio1 public class SimpleMain { JobQueue queue = new SimpleJobQueue ( 10 ); CyclicBarrier barrier = new CyclicBarrier ( 3 ); public void launch  ( int  base  ) { launchThreads( queue,  base); try {  barrier.await(); } catch (Exception e) {  e.printStackTrace(); } System. out .println(&quot;-----------------------------------&quot;); System. out .println(&quot;All threads completed their job. &quot; + queue); } private void launchThreads(JobQueue queue,  int blockNumber){ for (int i=0; i < 10; i++) {   new Thread( new  SimpleWorker(&quot;worker&quot; + (i + blockNumber) , queue, i,) ).start();  } } public static void main(String[] s) { int base = 0; if ( s.length==1 )  {  base = Integer. parseInt (s[0]); } new SimpleMain().launch ( base );   try {  barrier.await();  } catch ( Exception  e ) {  e.printStackTrace();  } } }
Esempio1 Ed infine questa è la configurazione di Terracotta per ottenere quanto descritto <? xml   version = &quot;1.0&quot;   encoding = &quot;UTF-8&quot; ?> < con:tc-config   xmlns:con = &quot;http://www.terracotta.org/config&quot; > < system > < configuration-model > development </ configuration-model > </ system > < servers > < server   name = &quot;localhost&quot; > < dso-port > 9510 </ dso-port > < jmx-port > 9520 </ jmx-port > < data > terracotta/server-data </ data > < logs > terracotta/server-logs </ logs > </ server > </ servers > < clients > < logs > terracotta/client-logs </ logs > </ clients > </ con:tc-config >
Esempio1 < application > < dso > < instrumented-classes > < include > < class-expression > *..* </ class-expression > </ include > </ instrumented-classes > < locks > < autolock > < method-expression > * *..*(..) </ method-expression > < lock-level > write </ lock-level > </ autolock > </ locks > < roots > < root > < field-name > it.jugmilano.terracotta.example1.main.SimpleMain.queue </ field-name > </ root > < root > < field-name > it.jugmilano.terracotta.example1.main.SimpleMain.barrier </ field-name > </ root > </ roots > </ dso > </ application >
Esempio1 A questo punto lanciamo il server Trerracotta ed infine, mandiamo in esecuzione il nostro esempio set /p base=Please enter thread name base: cd C:rogrammiavaorkspacesugTerracottaargetlasses dso-java -Dtc.config=../tc-config-simple-worker.xml it.jugmilano.terracotta.example1.main.SimpleMain %base% Lanciamo questo semplice script in tre diverse shell ed il gioco è fatto Quando il server Terracotta è in esecuzione, lanciare un main Java con Terracotta abilitato comporta semplicemente lanciare l'interprete java con pochi JVM arguments. Se vogliamo risparmiarci questa incombenza, possiamo utilizzare lo script  dso-java  che lo fa automaticamente
Esempio1 Prendiamo in esame le prime linee di codice significative JobQueue queue = new SimpleJobQueue ( 10 ); CyclicBarrier barrier = new CyclicBarrier ( 33 ); In esse vengono sullo heap due diversi oggetti;  ma su quale heap? E' chiaro che senza Terracotta, le tre vm in esecuzione lavorerebbero su tre spazi di memoria completamente separati, e quindi non ci sarebbe modo di far condividere la coda e la barriera a tutti i threads in esecuzione. E' evidente che questi due oggetti devono essere delle root, ossia oggetti che Terracotta costruisce sullo heap condiviso e sui quali ha pieno controllo. In tal modo, alla prima assegnazione, Terracotta alloca sul clustered heap entrambi gli oggetti.
Esempio1 Primo main esegue: JobQueue queue = new SimpleJobQueue ( 10 ); La variabile queue è dichiarata come root, Terracotta verifica che sullo heap condiviso essa non è ancora mai stata assegnata e procede alla sua istanziazione. Quando il secondo ed il terzo main eseguono la stessa operazione, Terracotta accede al clustered heap, verifica che la root queue è già stata assegnata, ed  ignora  l'istruzione. Tutte le successive operazioni sulla variabile queue  (e sul grafo di oggetti ad essa collegati)  avvengono sul clustered heap e NON sul local heap.
Esempio1 JVM  X augmented bytecode bytecode
Esempio2 Il nostro primo esempio ha diversi difetti In particolare la gestione della sincronizzazione è un evidente collo di bottiglia synchronized ( queue ) { job = queue.getJobAt( index ); status = job.getStatus(); log(&quot;Extracted Job &quot; +  index  + &quot; in status &quot; + status); } Questo codice guadagna un lock esclusivo sull'oggetto queue, impedendo ad ogni altro thread di accedervi (altri eventuali thread si bloccano nell'entry set). Essendo tuttavia queue definito come root ed essendo la classe SimpleWorker una instrumented class,  tutti i thread di tutte le Jvm in esecuzione nel cluster devono onorare tale lock  !!
Esempio2 Proviamo a fare di meglio, diminuendo le contese. In particolare, poiché nel nostro esempio semplificato ciascun thread sa a priori a quale Job della coda deve accedere, potremmo dividere la politica di locking a livello del singolo job. public   class  ConcurrentJobQueue  implements  JobQueue{ private  List<Job> jobs =  new  ArrayList<Job>(); private  List<Lock> locks =  new  ArrayList<Lock>() ; public  ConcurrentJobQueue( int  size){ for  ( int  i=0;i<size;i++){ jobs.add( new  SimpleJob()); locks.add( new  ReentrantLock()); } } public  Job getJobAt( int  index) { return  jobs.get(index); } public  Lock getLockAt( int  index) { return  locks.get(index); }
Esempio2 Adesso i nostri worker cambiano la propria politica di accesso alla coda public   void  run() { int  status; Job job; queue.getLockAt(index).lock(); job = queue.getJobAt(index); status = job.getStatus(); log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + status); if  (status == Job. NOT_ASSIGNED ) { job.changeStatus(Job. ASSIGNED ); log(&quot;Assigned Job &quot; + index); queue.getLockAt(index).unlock(); t ry  { Thread. sleep (1000);  }  catch  (InterruptedException e) { e.printStackTrace();  } queue.getLockAt(index).lock(); job.changeStatus(Job. COMPLETED ); log(&quot;Completed Job &quot; + index); queue.getLockAt(index).unlock(); } else  { log  (“Nothing TO DO”); queue.getLockAt(index).unlock();   }  }
Esempio2 Il file di configurazione di Terracotta non deve essere rivoluzionato, anzi viene addirittura semplificato. < application > < dso > < instrumented-classes > < include > < class-expression > *..* </ class-expression > </ include > </ instrumented-classes > < roots > < root > < field-name > it.jugmilano.terracotta.example2.main.ConcurrentMain.queue </ field-name > </ root > < root > < field-name > it.jugmilano.terracotta.example2.main.ConcurrentMain.barrier </ field-name > </ root > </ roots > </ dso > </ application >
Esempio3 Complichiamo ancora lo scenario; finora i nostri threads lavorano in esclusione; il primo che guadagna l'accesso al job (entry della coda condivisa) effettua tutto il lavoro, gli altri aspettano passivamente. In uno scenario più realistico, diversi threads potrebbero lavorare in collaborazione tra loro.  Ad esempio il primo che guadagna l'accesso effettua un primo cambio di stato, il secondo un altro e così via fino ad arrivare nello stato finale ove il job è effettivamente stato completato. Per rendere le cose più interessanti, scriviamo un worker generico in grado di riconoscere lo stato del job corrente e di comportarsi di conseguenza. Questo comporta la  necessità di ripensare leggermente i diversi attori.
Condition Le Condition, a differenza dei classici metodi di Object, consentono al singolo oggetto di comportarsi come se avesse wait-set multipli,  Lock  ->  rimpiazza synchronized Condition  -> rimpiazza i metodi dei monitor di Object Le Conditions danno ai threads un mezzo a per sospendere la propria esecuzione (&quot; wait &quot;) fin quando notificati (“ signal ”) da un altro thread.  La Condition stessa è una shared resource, e per tale motivo dev'essere protetta da un lock.  Mettersi in wait su una Condition rilascia  atomicamente  il lock detenuto su di essa. Lock lock = new ReentrantLock(); Condition cA = lock.newCondition();  Condition cB = lock.newCondition();
Condition I metodi offerti da Condition sono analoghi a quelli di Object: void await()  sospende il thread corrente finquando  signalled  o  interrupted . Il lock associato con la Condition è atomicamente rilasciato ed il thread corrente diventa non più utilizabile dallo scheduler della JVM Il thread ritorna schedulabile solo quando un altro thread invoca  signal()  o  signalAll()  sulla stessa Condition,  Se il thread ritorna schedulabile, esso è in contesa per guadagnare il Lock. Prima che await() possa ritornare, il thread corrente deve riguadagnare il lock associato con la Condition.  Quando il thread riprende l'esecuzione,  è garantito che detenga il lock .
Esempio3 Per prima cosa il Job cambia definizione: public   interface  CollaborativeJob { public   static   final   int   INITIAL_STEP  = 0; public   static   final   int   STEP_1  = 1; public   static   final   int   STEP_2  = 2; public   static   final   int   FINAL_STEP  = 3; public   void  changeStatus( int  status); public   int  getStatus(); } public   class  SimpleCollaborativeJob  implements  CollaborativeJob { private   int  jobStatus; public  SimpleCollaborativeJob(){ jobStatus =  INITIAL_STEP ; } public   void  changeStatus( int  status) { this .jobStatus = status; } public   int  getStatus() { return  jobStatus; } }
Esempio3 Anche la coda necessita di essere modificata, aggiungendo le strutture necessarie ad implementare i meccanismi di collaborazione public   class  CollaborativeJobQueue  implements  JobQueue{ private  List<CollaborativeJob> jobs =  new  ArrayList<CollaborativeJob>(); private  List<Lock> locks =  new  ArrayList<Lock>(); private  List<Condition> waitSets =  new  ArrayList<Condition>();   public  CollaborativeJobQueue( int  size){ for  ( int  i=0;i<size;i++){ jobs.add( new  SimpleCollaborativeJob()); Lock lock =  new  ReentrantLock(); locks.add(lock);  waitSets.add(lock.newCondition()); } } public  CollaborativeJob getJobAt( int  index) { return  jobs.get(index); } public  Lock getLockAt( int  index) { return  locks.get(index); } public  Condition getWaitSetAt( int  index) { return  waitSets.get(index); }
Esempio3 Ed infine il nuovo worker public   void  run() { CollaborativeJob job; queue.getLockAt(index).lock(); job = queue.getJobAt (  index  );  try  { if  ( job.getStatus() != CollaborativeJob. FINAL_STEP ) { while  (desiredStatus - job.getStatus() > 1) { log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + job.getStatus() + &quot; waiting  desiredstatus= &quot; + desiredStatus); queue.getWaitSetAt(index).await(); } Thread. sleep (1000); job.changeStatus(desiredStatus); log(&quot;Worked on Job &quot; + index + &quot; new status= &quot; + job.getStatus() + &quot; desiredstatus= &quot; +  desiredStatus); log(&quot;Job &quot; + index + &quot; completed &quot;); queue.getWaitSetAt(index).signalAll(); queue.getLockAt(index).unlock(); }  else  { log(&quot;Nothing TO DO status= &quot; + job.getStatus() + &quot; desiredstatus= &quot; + desiredStatus); queue.getLockAt(index).unlock(); } }  catch  (InterruptedException e) { queue.getLockAt(index).unlock(); e.printStackTrace(); } }
Esempio3 Come cambia la configurazione di Terracotta? In nessun modo! Terracotta onora la semantica di java.util.concurrent, non occorre nessuna configurazione particolare. L'unico compito richiesto allo sviluppatore è la correttezza del comportamento run time e l'assenza di  race-conditions  ed errori  time-depending , esattamente come se si programmasse in un ambiente concorrente puro. Quest'ultimo esempio illustra perfettamente la totale trasparenza di Terracotta nel fornire allo sviluppatore un ambiente clusterizzato ed un  reale parallelismo tra i threads java. Si pensi per contro a quanto sarebbe stato più complicato ed  invasivo  realizzare questa stessa soluzione utilizzando altre soluzioni del mondo Java (database, JMS, RMI...)
Struttura esempi

Más contenido relacionado

La actualidad más candente

Twcrashcourse
TwcrashcourseTwcrashcourse
Twcrashcourserik0
 
Programmazione funzionale e Stream in Java
Programmazione funzionale e Stream in JavaProgrammazione funzionale e Stream in Java
Programmazione funzionale e Stream in JavaCristina Attori
 
javaday 2006 - Tiger
javaday 2006 - Tigerjavaday 2006 - Tiger
javaday 2006 - TigerMatteo Baccan
 
PHP Object Injection Demystified
PHP Object Injection DemystifiedPHP Object Injection Demystified
PHP Object Injection Demystified_EgiX
 
Two months of Kotlin
Two months of KotlinTwo months of Kotlin
Two months of KotlinErik Minarini
 
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...Massimiliano Leone
 

La actualidad más candente (11)

Twcrashcourse
TwcrashcourseTwcrashcourse
Twcrashcourse
 
Programmazione funzionale e Stream in Java
Programmazione funzionale e Stream in JavaProgrammazione funzionale e Stream in Java
Programmazione funzionale e Stream in Java
 
Programming iOS lezione 3
Programming iOS lezione 3Programming iOS lezione 3
Programming iOS lezione 3
 
Corso Java 2 - AVANZATO
Corso Java 2 - AVANZATOCorso Java 2 - AVANZATO
Corso Java 2 - AVANZATO
 
javaday 2006 - Tiger
javaday 2006 - Tigerjavaday 2006 - Tiger
javaday 2006 - Tiger
 
Java5
Java5Java5
Java5
 
Groovy & Grails
Groovy & GrailsGroovy & Grails
Groovy & Grails
 
PHP Object Injection Demystified
PHP Object Injection DemystifiedPHP Object Injection Demystified
PHP Object Injection Demystified
 
Two months of Kotlin
Two months of KotlinTwo months of Kotlin
Two months of Kotlin
 
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...
A short introduction about traffic shaping and K-Shaper tool --- speech at Ha...
 
Java lezione 8
Java lezione 8Java lezione 8
Java lezione 8
 

Similar a Terracotta JUG Milano

Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)
Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)
Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)DotNetMarche
 
Java Unit Testing - In container and database testing
Java Unit Testing - In container and database testingJava Unit Testing - In container and database testing
Java Unit Testing - In container and database testingfgianneschi
 
Introduzione al linguaggio Java
Introduzione al linguaggio JavaIntroduzione al linguaggio Java
Introduzione al linguaggio JavaPaolo Tosato
 
Programmazione a oggetti tramite la macchina del caffé (pt. 3)
Programmazione a oggetti tramite la macchina del caffé (pt. 3)Programmazione a oggetti tramite la macchina del caffé (pt. 3)
Programmazione a oggetti tramite la macchina del caffé (pt. 3)Marcello Missiroli
 
Webbit 2004: Tiger, java
Webbit 2004: Tiger, javaWebbit 2004: Tiger, java
Webbit 2004: Tiger, javaMatteo Baccan
 
[drupalday2017] - Async navigation with a lightweight ES6 framework
[drupalday2017] - Async navigation with a lightweight ES6 framework[drupalday2017] - Async navigation with a lightweight ES6 framework
[drupalday2017] - Async navigation with a lightweight ES6 frameworkDrupalDay
 
Async navigation with a lightweight ES6 framework
Async navigation with a lightweight ES6 frameworkAsync navigation with a lightweight ES6 framework
Async navigation with a lightweight ES6 frameworksparkfabrik
 
Scala Programming Linux Day 2009
Scala Programming Linux Day 2009Scala Programming Linux Day 2009
Scala Programming Linux Day 2009Massimiliano Dessì
 
Javascript avanzato: sfruttare al massimo il web
Javascript avanzato: sfruttare al massimo il webJavascript avanzato: sfruttare al massimo il web
Javascript avanzato: sfruttare al massimo il webRoberto Messora
 
JSP Tag Library
JSP Tag LibraryJSP Tag Library
JSP Tag Libraryjgiudici
 
JSP Tag Library
JSP Tag LibraryJSP Tag Library
JSP Tag Libraryjgiudici
 

Similar a Terracotta JUG Milano (20)

Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)
Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)
Potenza e controllo con le Parallel Libraries (Raffaele Rialdi)
 
Java Advanced
Java AdvancedJava Advanced
Java Advanced
 
Java lezione 15
Java lezione 15Java lezione 15
Java lezione 15
 
Java codestyle & tipstricks
Java codestyle & tipstricksJava codestyle & tipstricks
Java codestyle & tipstricks
 
[Objective-C] - Introduzione
[Objective-C] - Introduzione[Objective-C] - Introduzione
[Objective-C] - Introduzione
 
Many Designs Elements
Many Designs ElementsMany Designs Elements
Many Designs Elements
 
Java Unit Testing - In container and database testing
Java Unit Testing - In container and database testingJava Unit Testing - In container and database testing
Java Unit Testing - In container and database testing
 
Introduzione al linguaggio Java
Introduzione al linguaggio JavaIntroduzione al linguaggio Java
Introduzione al linguaggio Java
 
Programmazione a oggetti tramite la macchina del caffé (pt. 3)
Programmazione a oggetti tramite la macchina del caffé (pt. 3)Programmazione a oggetti tramite la macchina del caffé (pt. 3)
Programmazione a oggetti tramite la macchina del caffé (pt. 3)
 
Webbit 2004: Tiger, java
Webbit 2004: Tiger, javaWebbit 2004: Tiger, java
Webbit 2004: Tiger, java
 
Ajax - Presente e futuro delle applicazioni web
Ajax - Presente e futuro delle applicazioni webAjax - Presente e futuro delle applicazioni web
Ajax - Presente e futuro delle applicazioni web
 
[drupalday2017] - Async navigation with a lightweight ES6 framework
[drupalday2017] - Async navigation with a lightweight ES6 framework[drupalday2017] - Async navigation with a lightweight ES6 framework
[drupalday2017] - Async navigation with a lightweight ES6 framework
 
Async navigation with a lightweight ES6 framework
Async navigation with a lightweight ES6 frameworkAsync navigation with a lightweight ES6 framework
Async navigation with a lightweight ES6 framework
 
unreal IRCd 3281
unreal IRCd 3281unreal IRCd 3281
unreal IRCd 3281
 
Jug 30 10 04 Jdo
Jug 30 10 04 JdoJug 30 10 04 Jdo
Jug 30 10 04 Jdo
 
Scala Programming Linux Day 2009
Scala Programming Linux Day 2009Scala Programming Linux Day 2009
Scala Programming Linux Day 2009
 
Javascript avanzato: sfruttare al massimo il web
Javascript avanzato: sfruttare al massimo il webJavascript avanzato: sfruttare al massimo il web
Javascript avanzato: sfruttare al massimo il web
 
SCBCD 1. generic ejb
SCBCD 1. generic ejbSCBCD 1. generic ejb
SCBCD 1. generic ejb
 
JSP Tag Library
JSP Tag LibraryJSP Tag Library
JSP Tag Library
 
JSP Tag Library
JSP Tag LibraryJSP Tag Library
JSP Tag Library
 

Terracotta JUG Milano

  • 1. Clustering di applicazioni Java SE con Terracotta Guido Anselmi [email_address] Java User Group Milano http://www.jugmilano.it
  • 2.
  • 4. Le basi del clustering in Terracotta
  • 5. Object Roots e Clustered Objects
  • 9. Casi d'uso e definizioni Terracotta is a transparent clustering service for Java applications I tipici casi d'uso di Terractotta possono rientrare nelle seguenti macro categorie: • Distributed cache • Database offload • Session replication • Workload partitioning
  • 10. Overview Terracotta è un servizio di clustering trasparente . “ Trasparente ” è riferito al linguaggio Java, in cui il clustering è ottenuto senza alcun modello di programmazione o API specifiche. “ Clustering ” indica la capacità di consentire a macchine e processi Java multipli di lavorare insieme sugli stessi dati e di poter comunicare tra di loro utilizzando semplicamente oggetti in memoria e threading logic. Terracotta consente alle applicazioni Java di girare su quante macchine necessario, senza alcuna modifica specifica. Non viene alcun modello specifico di programmazione come clustered caching, EJB, o anche Hibernate o Spring. Terracotta può prendere un'applicazione esistente e scalarla trasparentemente su molte macchine.
  • 11. POJO Clustering-Instrumented classes Affinchè Terracotta possa effettivamente operare sul nostro codice ( bytecode in realtà), occorre indicare nella configurazione quali classi devono essere “ispezionate e controllate”. Questo avviene tramite il file di configurazione, nella seguente sezione: < instrumented-classes > < include > < class-expression > it.jugmilano.terracotta.example1 ..* </ class-expression > </ include > </ instrumented-classes > Quali classi devono essere instrumented? Per ragioni di performance è bene limitare l'insieme a quello delle classi che usano root objects.
  • 12. POJO Clustering - Roots Un oggetto definito come root è l'elemento radice di un grafo di oggetti clusterizzati. Qualunque variabile di una classe può esere dichiarata come root. La prima volta che nel codice java viene assegnato un valore ad un oggetto root, Terracotta trasforma lo stesso in clustered object, allocandolo sullo heap condiviso. In questo processo, Terracotta naviga l'intero grafo degli oggetti raggiungibili dalla root, ed essi vengono a loro volta allocati sullo heap condiviso. Le roots sono dichiarate nel file di configurazione di Terracotta <roots> ... <root> <field-name> my.package.MyClass.myRootField </field-name> </root> </roots>
  • 13. POJO Clustering - Roots I fields dichiarati come root assumono una speciale semantica 1) La prima volta che un field root viene assegnato da una JVM, la root è creata nel cluster Terracotta. 2) Una volta assegnato, il valore di un field root non può essere cambiato. Tutti i successivi assegnamenti, di qualunque JVM, vengono ignorati . 3) L'oggetto top level di un grafo root, non viene mai reclamato dal garbage collector distribuito di Terracotta. Il server Terracotta ha un garbage collector distribuito che rimuove i clustered objects non più referenziati, ossia non più raggiungibili da nessuna root (non facenti parte del grafo di nessuna root) Le roots hanno lo stesso ciclo di vita del clustered heap, e non sono legate alla singola JVM.
  • 14. POJO Clustering - Roots Ma come fa terracotta a trasformare in maniera trasparente una variabile d'istanza o statica in un oggetto condiviso? Terracotta opera esclusivamente a livello di bytecode. Guidato da quanto indicato nella sezione instrumented-classes, opera una ricerca sulla presenza di oggetti root, e ne trasforma il bytecode. Terracotta si inserisce tra la logica applicativa e la memoria della JVM, ispezionando tutte le chiamate allo heap che coinvolgono roots objects. In particolare, tutte le letture/scritture sullo heap che coinvolgono root objects vengono sostituite da letture/scritture sul clustered heap. HEAPREAD() -> CLUSTEREDHEAPREAD() HEAPWRITE() -> CLUSTEREDHEAPWRITE()
  • 15. POJO Clustering - Locks Quando diversi threads accedono concorrentemente gli stessi dati sullo heap, speciali precauzioni devono essere prese per evitare interferenze distruttive tra di essi. Naturalmente la cosa è ancora valida quando diversi threads su diverse jvm accedono contemporaneamente lo stesso oggetto sul clustered heaps. Su singola JVM, java sin dagli albori offre meccanismi integrati di mutua esclusione e collaborazione, nella forma del costrutto synchronized e dei metodi di Object wait e notify. Nelle ultime versioni del linguaggio è stato inoltre integrato il package java.util.concurrent , rendendo java un linguaggio con un supporto di primissimo livello al multithreading.
  • 16. POJO Clustering - Locks Cosa deve cambiare nel codice quando mutua escliusione e collaborazione assumono una valenza a livello di cluster? Assolutamente nulla! Terracotta fornisce esattamente le stesse garanzie di serializzazione degli accessi, coordinamento e visibilità a thread in differenti JVM rispetto a quanto fa la singola JVM sui suoi threads/heap. I meccanismi che Terracotta utilizza per ottemperare a queste semantiche sono i locks. Terracotta amplia la semantica dei lock built in di Java in modo da dargli un effetto a livello di cluster. Il lock clusterizzato è iniettato nel bytecode in base a quanto specificato nella sezione lock
  • 17. POJO Clustering - Locks <locks> <autolock> <lock-level>write</lock-level> <method-expression>void HelloWorld.method(..)</method-expression> </autolock> </locks> Questa configurazione istruisce terracotta di ispezionare tutti i metodi che matchano la method-expression dentro le instrumented-classes, verificare se al loro interno ci sono accessi synchronized a fields root e, nel caso, sostituire il lock jvm con uno clustered. In luogo di <autolock> è possibile specificare dei <named-locks> La differenza tra i due è che il secondo agisce sempre a livello di intero metodo, mentre il primo onora i blocchi synchronized presenti dentro i metodi.
  • 18. POJO Clustering - Locks A differenza di Java, i lock di Terracotta hanno diversi livelli: • write • synchronous-write • read locks Write locks: sono i classici lock di mutua esclusione: essi garantiscono che solo un thread nell'intero cluster possa ottenere l'accesso all'oggetto protetto. Synchronous write locks aggiungono la garanzia che il lock non sia rilasciato fin quando le modifiche effettuate dal thread corrente non siano effettivamente propagate al cluster. Read locks: consentono a diversi thread di acquisire il lock sull'oggetto, a patto di non effettuare nessun operazione di scrittura (pena eccezione runtime) Nessun thread può acquisire un lock a livello write se altri thread detengono un lock a livello read. Inoltre a nessun thread è consentito di ottenere un lock read se un altro thread detiene un lock a livello write.
  • 19. Esempio1 Immaginiamo di avere un insieme di threads il cui compito consiste unicamente nell'estrarre dei Job da un coda condivisa, elaborarli e cambiarne lo stato una volta terminato. Tali threads non devono necessariamente essere eseguiti sulla stessa JVM Job1 Job1 Job1 Job1 Job1 Job1 Jobn Job4 Job3 Job2 JVM1 thread1-3 thread1-2 thread1-1 JVM2 thread2-3 thread2-2 thread2-1 JVM3 thread3-3 thread3-2 thread3-1
  • 20. Esempio1 La coda è evidentemente un oggetto condiviso, quindi va protetta dall'accesso concorrente da parte di thread differenti (che siano sulla stessa o su diverse JVM) Una prima, molto semplificata, soluzione è la seguente: public class SimpleJobQueue implements JobQueue { private List<Job> jobs = new ArrayList<Job>(); public SimpleJobQueue(int size){ for (int i=0;i<size;i++){ jobs.add(new SimpleJob()); } } public Job getJob(Job job) { return null; } public Job getJobAt(int index) { return jobs.get(index); } }
  • 21. Esempio1 Dove Job è un'interfaccia che potremmo implementare nel modo seguente public interface Job { public static final int NOT_ASSIGNED = 0; public static final int ASSIGNED = 1; public static final int COMPLETED = 2; public void changeStatus(int status); public int getStatus(); } public class SimpleJob implements Job { private int jobStatus; public SimpleJob(){ jobStatus = NOT_ASSIGNED ; } public void changeStatus(int status) { this.jobStatus = status; } public int getStatus() { return jobStatus; } }
  • 22. Esempio1 Ed infine il terzo attore, il thread che fa il lavoro sporco, prelevandolo dalla coda. public class SimpleWorker implements Runnable { .................................... public void run() { int status; Job job; synchronized ( queue ) { job = queue.getJobAt( index ); status = job.getStatus(); log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + status); if (status == Job. NOT_ASSIGNED ) { job.changeStatus(Job. ASSIGNED ); log(&quot;Assigned Job &quot; + index ); try {Thread. sleep (1000);} catch(InterruptedException e){ } } job.changeStatus(Job. COMPLETED ); log(&quot;Completed Job &quot; + index ); } else { log(&quot;Job &quot; + index + &quot; Allready Assigned - Nothing TO DO&quot;); } } }
  • 23. Esempio1 Vogliamo adesso testare il nostro piccolo sistema, costruendo una coda contenente 10 jobs e lanciando 30 diversi threads che accedono la coda stessa, in tre diverse JVM. Senza troppa sorpresa, occorre scrivere una classe con un metodo main e lanciarlo tre volte, ad esempio passandolo all'interprete java in tre diverse shell. Proviamo a scriverlo.
  • 24. Esempio1 public class SimpleMain { JobQueue queue = new SimpleJobQueue ( 10 ); CyclicBarrier barrier = new CyclicBarrier ( 3 ); public void launch ( int base ) { launchThreads( queue, base); try { barrier.await(); } catch (Exception e) { e.printStackTrace(); } System. out .println(&quot;-----------------------------------&quot;); System. out .println(&quot;All threads completed their job. &quot; + queue); } private void launchThreads(JobQueue queue, int blockNumber){ for (int i=0; i < 10; i++) { new Thread( new SimpleWorker(&quot;worker&quot; + (i + blockNumber) , queue, i,) ).start(); } } public static void main(String[] s) { int base = 0; if ( s.length==1 ) { base = Integer. parseInt (s[0]); } new SimpleMain().launch ( base ); try { barrier.await(); } catch ( Exception e ) { e.printStackTrace(); } } }
  • 25. Esempio1 Ed infine questa è la configurazione di Terracotta per ottenere quanto descritto <? xml version = &quot;1.0&quot; encoding = &quot;UTF-8&quot; ?> < con:tc-config xmlns:con = &quot;http://www.terracotta.org/config&quot; > < system > < configuration-model > development </ configuration-model > </ system > < servers > < server name = &quot;localhost&quot; > < dso-port > 9510 </ dso-port > < jmx-port > 9520 </ jmx-port > < data > terracotta/server-data </ data > < logs > terracotta/server-logs </ logs > </ server > </ servers > < clients > < logs > terracotta/client-logs </ logs > </ clients > </ con:tc-config >
  • 26. Esempio1 < application > < dso > < instrumented-classes > < include > < class-expression > *..* </ class-expression > </ include > </ instrumented-classes > < locks > < autolock > < method-expression > * *..*(..) </ method-expression > < lock-level > write </ lock-level > </ autolock > </ locks > < roots > < root > < field-name > it.jugmilano.terracotta.example1.main.SimpleMain.queue </ field-name > </ root > < root > < field-name > it.jugmilano.terracotta.example1.main.SimpleMain.barrier </ field-name > </ root > </ roots > </ dso > </ application >
  • 27. Esempio1 A questo punto lanciamo il server Trerracotta ed infine, mandiamo in esecuzione il nostro esempio set /p base=Please enter thread name base: cd C:rogrammiavaorkspacesugTerracottaargetlasses dso-java -Dtc.config=../tc-config-simple-worker.xml it.jugmilano.terracotta.example1.main.SimpleMain %base% Lanciamo questo semplice script in tre diverse shell ed il gioco è fatto Quando il server Terracotta è in esecuzione, lanciare un main Java con Terracotta abilitato comporta semplicemente lanciare l'interprete java con pochi JVM arguments. Se vogliamo risparmiarci questa incombenza, possiamo utilizzare lo script dso-java che lo fa automaticamente
  • 28. Esempio1 Prendiamo in esame le prime linee di codice significative JobQueue queue = new SimpleJobQueue ( 10 ); CyclicBarrier barrier = new CyclicBarrier ( 33 ); In esse vengono sullo heap due diversi oggetti; ma su quale heap? E' chiaro che senza Terracotta, le tre vm in esecuzione lavorerebbero su tre spazi di memoria completamente separati, e quindi non ci sarebbe modo di far condividere la coda e la barriera a tutti i threads in esecuzione. E' evidente che questi due oggetti devono essere delle root, ossia oggetti che Terracotta costruisce sullo heap condiviso e sui quali ha pieno controllo. In tal modo, alla prima assegnazione, Terracotta alloca sul clustered heap entrambi gli oggetti.
  • 29. Esempio1 Primo main esegue: JobQueue queue = new SimpleJobQueue ( 10 ); La variabile queue è dichiarata come root, Terracotta verifica che sullo heap condiviso essa non è ancora mai stata assegnata e procede alla sua istanziazione. Quando il secondo ed il terzo main eseguono la stessa operazione, Terracotta accede al clustered heap, verifica che la root queue è già stata assegnata, ed ignora l'istruzione. Tutte le successive operazioni sulla variabile queue (e sul grafo di oggetti ad essa collegati) avvengono sul clustered heap e NON sul local heap.
  • 30. Esempio1 JVM X augmented bytecode bytecode
  • 31. Esempio2 Il nostro primo esempio ha diversi difetti In particolare la gestione della sincronizzazione è un evidente collo di bottiglia synchronized ( queue ) { job = queue.getJobAt( index ); status = job.getStatus(); log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + status); } Questo codice guadagna un lock esclusivo sull'oggetto queue, impedendo ad ogni altro thread di accedervi (altri eventuali thread si bloccano nell'entry set). Essendo tuttavia queue definito come root ed essendo la classe SimpleWorker una instrumented class, tutti i thread di tutte le Jvm in esecuzione nel cluster devono onorare tale lock !!
  • 32. Esempio2 Proviamo a fare di meglio, diminuendo le contese. In particolare, poiché nel nostro esempio semplificato ciascun thread sa a priori a quale Job della coda deve accedere, potremmo dividere la politica di locking a livello del singolo job. public class ConcurrentJobQueue implements JobQueue{ private List<Job> jobs = new ArrayList<Job>(); private List<Lock> locks = new ArrayList<Lock>() ; public ConcurrentJobQueue( int size){ for ( int i=0;i<size;i++){ jobs.add( new SimpleJob()); locks.add( new ReentrantLock()); } } public Job getJobAt( int index) { return jobs.get(index); } public Lock getLockAt( int index) { return locks.get(index); }
  • 33. Esempio2 Adesso i nostri worker cambiano la propria politica di accesso alla coda public void run() { int status; Job job; queue.getLockAt(index).lock(); job = queue.getJobAt(index); status = job.getStatus(); log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + status); if (status == Job. NOT_ASSIGNED ) { job.changeStatus(Job. ASSIGNED ); log(&quot;Assigned Job &quot; + index); queue.getLockAt(index).unlock(); t ry { Thread. sleep (1000); } catch (InterruptedException e) { e.printStackTrace(); } queue.getLockAt(index).lock(); job.changeStatus(Job. COMPLETED ); log(&quot;Completed Job &quot; + index); queue.getLockAt(index).unlock(); } else { log (“Nothing TO DO”); queue.getLockAt(index).unlock(); } }
  • 34. Esempio2 Il file di configurazione di Terracotta non deve essere rivoluzionato, anzi viene addirittura semplificato. < application > < dso > < instrumented-classes > < include > < class-expression > *..* </ class-expression > </ include > </ instrumented-classes > < roots > < root > < field-name > it.jugmilano.terracotta.example2.main.ConcurrentMain.queue </ field-name > </ root > < root > < field-name > it.jugmilano.terracotta.example2.main.ConcurrentMain.barrier </ field-name > </ root > </ roots > </ dso > </ application >
  • 35. Esempio3 Complichiamo ancora lo scenario; finora i nostri threads lavorano in esclusione; il primo che guadagna l'accesso al job (entry della coda condivisa) effettua tutto il lavoro, gli altri aspettano passivamente. In uno scenario più realistico, diversi threads potrebbero lavorare in collaborazione tra loro. Ad esempio il primo che guadagna l'accesso effettua un primo cambio di stato, il secondo un altro e così via fino ad arrivare nello stato finale ove il job è effettivamente stato completato. Per rendere le cose più interessanti, scriviamo un worker generico in grado di riconoscere lo stato del job corrente e di comportarsi di conseguenza. Questo comporta la necessità di ripensare leggermente i diversi attori.
  • 36. Condition Le Condition, a differenza dei classici metodi di Object, consentono al singolo oggetto di comportarsi come se avesse wait-set multipli, Lock -> rimpiazza synchronized Condition -> rimpiazza i metodi dei monitor di Object Le Conditions danno ai threads un mezzo a per sospendere la propria esecuzione (&quot; wait &quot;) fin quando notificati (“ signal ”) da un altro thread. La Condition stessa è una shared resource, e per tale motivo dev'essere protetta da un lock. Mettersi in wait su una Condition rilascia atomicamente il lock detenuto su di essa. Lock lock = new ReentrantLock(); Condition cA = lock.newCondition(); Condition cB = lock.newCondition();
  • 37. Condition I metodi offerti da Condition sono analoghi a quelli di Object: void await() sospende il thread corrente finquando signalled o interrupted . Il lock associato con la Condition è atomicamente rilasciato ed il thread corrente diventa non più utilizabile dallo scheduler della JVM Il thread ritorna schedulabile solo quando un altro thread invoca signal() o signalAll() sulla stessa Condition, Se il thread ritorna schedulabile, esso è in contesa per guadagnare il Lock. Prima che await() possa ritornare, il thread corrente deve riguadagnare il lock associato con la Condition. Quando il thread riprende l'esecuzione, è garantito che detenga il lock .
  • 38. Esempio3 Per prima cosa il Job cambia definizione: public interface CollaborativeJob { public static final int INITIAL_STEP = 0; public static final int STEP_1 = 1; public static final int STEP_2 = 2; public static final int FINAL_STEP = 3; public void changeStatus( int status); public int getStatus(); } public class SimpleCollaborativeJob implements CollaborativeJob { private int jobStatus; public SimpleCollaborativeJob(){ jobStatus = INITIAL_STEP ; } public void changeStatus( int status) { this .jobStatus = status; } public int getStatus() { return jobStatus; } }
  • 39. Esempio3 Anche la coda necessita di essere modificata, aggiungendo le strutture necessarie ad implementare i meccanismi di collaborazione public class CollaborativeJobQueue implements JobQueue{ private List<CollaborativeJob> jobs = new ArrayList<CollaborativeJob>(); private List<Lock> locks = new ArrayList<Lock>(); private List<Condition> waitSets = new ArrayList<Condition>(); public CollaborativeJobQueue( int size){ for ( int i=0;i<size;i++){ jobs.add( new SimpleCollaborativeJob()); Lock lock = new ReentrantLock(); locks.add(lock); waitSets.add(lock.newCondition()); } } public CollaborativeJob getJobAt( int index) { return jobs.get(index); } public Lock getLockAt( int index) { return locks.get(index); } public Condition getWaitSetAt( int index) { return waitSets.get(index); }
  • 40. Esempio3 Ed infine il nuovo worker public void run() { CollaborativeJob job; queue.getLockAt(index).lock(); job = queue.getJobAt ( index ); try { if ( job.getStatus() != CollaborativeJob. FINAL_STEP ) { while (desiredStatus - job.getStatus() > 1) { log(&quot;Extracted Job &quot; + index + &quot; in status &quot; + job.getStatus() + &quot; waiting desiredstatus= &quot; + desiredStatus); queue.getWaitSetAt(index).await(); } Thread. sleep (1000); job.changeStatus(desiredStatus); log(&quot;Worked on Job &quot; + index + &quot; new status= &quot; + job.getStatus() + &quot; desiredstatus= &quot; + desiredStatus); log(&quot;Job &quot; + index + &quot; completed &quot;); queue.getWaitSetAt(index).signalAll(); queue.getLockAt(index).unlock(); } else { log(&quot;Nothing TO DO status= &quot; + job.getStatus() + &quot; desiredstatus= &quot; + desiredStatus); queue.getLockAt(index).unlock(); } } catch (InterruptedException e) { queue.getLockAt(index).unlock(); e.printStackTrace(); } }
  • 41. Esempio3 Come cambia la configurazione di Terracotta? In nessun modo! Terracotta onora la semantica di java.util.concurrent, non occorre nessuna configurazione particolare. L'unico compito richiesto allo sviluppatore è la correttezza del comportamento run time e l'assenza di race-conditions ed errori time-depending , esattamente come se si programmasse in un ambiente concorrente puro. Quest'ultimo esempio illustra perfettamente la totale trasparenza di Terracotta nel fornire allo sviluppatore un ambiente clusterizzato ed un reale parallelismo tra i threads java. Si pensi per contro a quanto sarebbe stato più complicato ed invasivo realizzare questa stessa soluzione utilizzando altre soluzioni del mondo Java (database, JMS, RMI...)
  • 43. Conclusioni Terracotta è uno strumento estremamente complesso e potente, ma allo stesso tempo semplice da utilizzare, avendo esperienza di programmazione concorrente. I concetti illustrati sono alla base di soluzioni di grid computing , realizzabili con basso costo e difficoltà ridotta implementativa. Ad esempio, tramite terracotta ed i concetti di root, lock ed instrumented-classes è abbastanza semplice realizzare un effettivo pattern master-worker , utilizzabile per sistemi di produzione ad alta affidabilità ed in grado di rispondere ad elevate esigenze prestazionali.